Skip to content

Commit

Permalink
Merge pull request #29 from AlgebraicJulia/fixPBPO
Browse files Browse the repository at this point in the history
Partial abstraction / variable substitution+merging
  • Loading branch information
kris-brown authored Oct 14, 2023
2 parents 8f785c7 + 60cce70 commit 8c4bde4
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 194 deletions.
157 changes: 30 additions & 127 deletions src/categorical_algebra/CSets.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module CSets
export extend_morphism, pushout_complement,
can_pushout_complement, dangling_condition, invert_hom, check_pb,
gluing_conditions, extend_morphisms, postcompose_partial, sub_vars,
Migrate, invert_iso, deattr, var_pullback, remove_freevars
gluing_conditions, extend_morphisms, sub_vars,
Migrate, invert_iso, deattr, var_pullback

using Catlab, Catlab.Theories
using Catlab.CategoricalAlgebra
Expand All @@ -24,66 +24,6 @@ using StructEquality
# Morphism search
#################

"""
Given a span of morphisms, we seek to find morphisms B → C that make a
commuting triangle if possible.
B
g ↗ ↘ ?
A ⟶ C
f
Accepts homomorphism search keyword arguments to constrain the Hom(B,C) search.
"""
function extend_morphisms(f::ACSetTransformation, g::ACSetTransformation;
initial=Dict(), kw...
)::Vector{ACSetTransformation}
init = combine_dicts!(extend_morphism_constraints(f,g), initial)
isnothing(init) ? [] : homomorphisms(codom(g), codom(f); initial=init, kw...)
end

"""
Combine a user-specified initial dict with that generated by constraints
`Initial` could contain vectors or int-keyed dicts as its data for each object.
"""
function combine_dicts!(init, initial)
if isnothing(init) return nothing end
for (k,vs) in collect(initial)
for (i, v) in (vs isa AbstractVector ? enumerate : collect)(vs)
if haskey(init[k], i)
if init[k][i] != v return nothing end
else
init[k][i] = v
end
end
end
return NamedTuple(init)
end

# default behavior for types that don't explicitly implement `is_isomorphic`
is_isomorphic(x) = is_monic(x) && is_epic(x) # should be upstreamed
"""
Convert a morphism X->A to a morphism L->H using a partial morphism G->H,
if possible.
A ↩ C → B
↑ ↑
X ↩⌜Y
This is a more categorical way to compute `update_agent` but for now will
remain unused unless we want to generalize rewriting schedules beyond ACSet
rewriting.
"""
function postcompose_partial(ACB::Span, XA::ACSetTransformation)
S = acset_schema(dom(XA))
YX, YC = var_pullback(Cospan(XA, left(ACB)))
if all(o->is_isomorphic(YX[o]), ob(S))
return invert_iso(YX) YC right(ACB)
end
end


"""
Invert some (presumed iso) components of an ACSetTransformation (given by s)
"""
Expand Down Expand Up @@ -314,23 +254,6 @@ end
# Subobjects
############

"""Recursively include anything, e.g. and edge includes its vertices """
function complete_subobj(X::ACSet, sub)
sub = Dict([k=>Set(v) for (k,v) in pairs(sub)])
S = acset_schema(X)
change = true
while change
change = false
for (f,c,d) in homs(S)
new_d = setdiff(Set(X[collect(sub[c]),f]), sub[d])
if !isempty(new_d)
change = true
union!(sub[d], new_d)
end
end
end
return Dict([k=>collect(v) for (k,v) in pairs(sub)])
end
"""Recursively delete anything, e.g. deleting a vertex deletes its edge"""
function cascade_subobj(X::ACSet, sub)
sub = Dict([k=>Set(v) for (k,v) in pairs(sub)])
Expand All @@ -357,32 +280,52 @@ end

"""
Given a value for each variable, create a morphism X → X′ which applies the
substitution. We do this via pushout.
substitution. We do this via pushout.
O --> X where C has AttrVars for `merge` equivalence classes
↓ and O has only AttrVars (sent to concrete values or eq classes
C in the map to C.
`subs` is a dictionary (keyed by attrtype names) of int-keyed dictionaries
`subs` and `merge` are dictionaries keyed by attrtype names
`subs` values are int-keyed dictionaries indicating binding, e.g.
`; subs = (Weight = Dict(1 => 3.20, 5 => 2.32), ...)`
`merge` values are vectors of vectors indicating equivalence classes, e.g.
`; merge = (Weight = [[2,3], [4,6]], ...)`
"""
function sub_vars(X::ACSet, subs::AbstractDict)
function sub_vars(X::ACSet, subs::AbstractDict=Dict(), merge::AbstractDict=Dict())
S = acset_schema(X)
O, C = [constructor(X)() for _ in 1:2]
ox_, oc_ = Dict(), Dict()
ox_, oc_ = Dict{Symbol, Any}(), Dict{Symbol,Any}()
for at in attrtypes(S)
d = get(subs, at, Dict())
ox_[at] = AttrVar.(filter(p->p keys(d) && !(d[p] isa AttrVar), parts(X,at)))
oc_[at] = [d[p.val] for p in ox_[at]]
oc_[at] = Any[d[p.val] for p in ox_[at]]
add_parts!(O, at, length(oc_[at]))
end

for eq in get(merge, at, [])
isempty(eq) && error("Cannot have empty eq class")
c = AttrVar(add_part!(C, at))
for var in eq
add_part!(O, at)
push!(ox_[at], AttrVar(var))
push!(oc_[at], c)
end
end
end
ox = ACSetTransformation(O,X; ox_...)
oc = ACSetTransformation(O,C; oc_...)
return first(legs(pushout(ox, oc)))
end


# TODO replace with CSetTransformation limit when Catlab 0.16 is released

"""
Take an ACSet pullback combinatorially and freely add variables for all
attribute subparts.
TODO do var_limit, more generally
This relies on implementation details of `abstract`.
"""
function var_pullback(c::Cospan{<:StructACSet{S,Ts}}) where {S,Ts}
Expand All @@ -409,46 +352,6 @@ function var_pullback(c::Cospan{<:StructACSet{S,Ts}}) where {S,Ts}
end


"""
We may replace some ...
"""
function remove_freevars(X::StructACSet{S}) where S
X = deepcopy(X)
d = Dict(map(attrtypes(S)) do at
vs = Set{Int}()
for f in attrs(S; to=at, just_names=true)
for v in filter(x->x isa AttrVar, X[f])
push!(vs, v.val)
end
end
# Get new variable IDs
svs = sort(collect(vs))
vdict = Dict(v=>k for (k,v) in enumerate(svs))
n_v = length(vdict)
rem_parts!(X,at, parts(X,at)[n_v+1:end])
for f in attrs(S; to=at, just_names=true)
for (v,fv) in filter(v_->v_[2] isa AttrVar,collect(enumerate(X[f])))
X[v,f] = AttrVar(vdict[fv.val])
end
end
return at => svs
end)
return X => d
end

function remove_freevars(f::ACSetTransformation; check::Bool=false)
S = acset_schema(dom(f))
!check || is_natural(f) || error("unnatural freevars input")
X, d = remove_freevars(dom(f))
comps = Dict{Symbol,Any}(o=>collect(f[o]) for o in ob(S))
for at in attrtypes(S)
comps[at] = collect(f[at])[d[at]]
end
res = ACSetTransformation(X, codom(f); comps...)
!check || is_natural(res) || error("unnatural freevars output")
return res
end

function deattr(X::StructACSet{S})::AnonACSet where S
P = Presentation(FreeSchema)
add_generators!(P, Ob(FreeSchema, objects(S)...))
Expand Down
Loading

0 comments on commit 8c4bde4

Please sign in to comment.