Skip to content

Commit

Permalink
Merge pull request #831 from kris-brown/compat_markdeleted
Browse files Browse the repository at this point in the history
Support for mark-as-deleted in acsets
  • Loading branch information
epatters authored Feb 7, 2024
2 parents 5dcb85d + cd4e84e commit f0680ef
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 64 deletions.
5 changes: 3 additions & 2 deletions src/categorical_algebra/ACSetsGATsInterop.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ function Presentation(s::BasicSchema{Symbol})
end

function DenseACSets.struct_acset(name::Symbol, parent, p::Presentation;
index::Vector=[], unique_index::Vector=[])
DenseACSets.struct_acset(name, parent, Schema(p); index, unique_index)
index::Vector=[], unique_index::Vector=[],
part_type::Type{<:PartsType}=IntParts)
DenseACSets.struct_acset(name, parent, Schema(p); index, unique_index, part_type)
end

function DenseACSets.DynamicACSet(
Expand Down
109 changes: 64 additions & 45 deletions src/categorical_algebra/CSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export ACSetMorphism,
StructTightACSetTransformation, TightACSetTransformation,
LooseACSetTransformation, SubACSet, SubCSet,
components, type_components, force,
naturality_failures, show_naturality_failures, is_natural,
naturality_failures, show_naturality_failures, is_natural, in_bounds,
abstract_attributes

using Base.Iterators: flatten
Expand All @@ -28,7 +28,7 @@ import ...Theories: ob, hom, dom, codom, compose, ⋅, id,
meet, , join, , top, ⊤, bottom, ⊥, ,

using ..FreeDiagrams, ..Limits, ..Subobjects, ..Sets, ..FinSets, ..FinCats
using ..FinSets: VarFunction, LooseVarFunction, IdentityFunction, VarSet
using ..FinSets: VarFunction, LooseVarFunction, IdentityFunction, VarSet, AbsVarFunction
import ..Limits: limit, colimit, universal
import ..Subobjects: Subobject, implies, , subtract, \, negate, ¬, non, ~
import ..Sets: SetOb, SetFunction, TypeSet
Expand Down Expand Up @@ -427,47 +427,51 @@ TightACSetTransformation(components, X::StructACSet{S}, Y::StructACSet{S}) where

# Component coercion

function coerce_components(S, components, X,Y)
function coerce_components(S, components, X::ACSet{<:PT}, Y) where PT
@assert keys(components) objects(S) attrtypes(S)
ocomps = NamedTuple(
c => coerce_component(c, get(components,c,1:0), nparts(X,c), nparts(Y,c))
for c in objects(S))
kw = Dict(map(types(S)) do c
c => PT <: MarkAsDeleted ? (dom_parts=parts(X,c), codom_parts=parts(Y,c)) : (;)
end)
ocomps = NamedTuple(map(objects(S)) do c
c => coerce_component(c, get(components, c, 1:0),
nparts(X,c), nparts(Y,c); kw[c]...)
end)
acomps = NamedTuple(map(attrtypes(S)) do c
c => coerce_attrvar_component(c, get(components,c,1:0),
TypeSet(X, c), TypeSet(Y, c), nparts(X,c), nparts(Y,c))
c => coerce_attrvar_component(c, get(components, c, 1:0),
TypeSet(X, c), TypeSet(Y, c), nparts(X,c), nparts(Y,c); kw[c]...)
end)
return merge(ocomps, acomps)
return merge(ocomps, acomps)
end

function coerce_component(ob::Symbol, f::FinFunction{Int,Int},
dom_size::Int, codom_size::Int)
dom_size::Int, codom_size::Int; kw...)
length(dom(f)) == dom_size || error("Domain error in component $ob")
length(codom(f)) == codom_size || error("Codomain error in component $ob")
return f
# length(codom(f)) == codom_size || error("Codomain error in component $ob") # codom size is now Maxpart not nparts
return f
end

coerce_component(::Symbol, f, dom_size::Int, codom_size::Int) =
FinFunction(f, dom_size, codom_size)
coerce_component(::Symbol, f, dom_size::Int, codom_size::Int; kw...) =
FinFunction(f, dom_size, codom_size; kw...)

function coerce_attrvar_component(
ob::Symbol, f::AbstractVector,::TypeSet{T}, ::TypeSet{T},
dom_size::Int, codom_size::Int) where {T}
dom_size::Int, codom_size::Int; kw...) where {T}
e = "Domain error in component $ob variable assignment $(length(f)) != $dom_size"
length(f) == dom_size || error(e)
return VarFunction{T}(f, FinSet(codom_size))
end

function coerce_attrvar_component(
ob::Symbol, f::VarFunction,::TypeSet{T},::TypeSet{T},
dom_size::Int, codom_size::Int) where {T}
dom_size::Int, codom_size::Int; kw...) where {T}
# length(dom(f.fun)) == dom_size || error("Domain error in component $ob: $(dom(f.fun))!=$dom_size")
length(f.codom) == codom_size || error("Codomain error in component $ob: $(f.fun.codom)!=$codom_size")
return f
end

function coerce_attrvar_component(
ob::Symbol, f::LooseVarFunction,d::TypeSet{T},cd::TypeSet{T′},
dom_size::Int, codom_size::Int) where {T,T′}
dom_size::Int, codom_size::Int; kw...) where {T,T′}
length(dom(f.fun)) == dom_size || error("Domain error in component $ob")
length(f.codom) == codom_size || error("Codomain error in component $ob: $(f.fun.codom)!=$codom_size")
# We do not check types (equality is too strict)
Expand All @@ -478,7 +482,7 @@ end

"""Coerce an arbitrary julia function to a LooseVarFunction assuming no variables"""
function coerce_attrvar_component(ob::Symbol, f::Function, d::TypeSet{T},cd::TypeSet{T′},
dom_size::Int, codom_size::Int) where {T,T′}
dom_size::Int, codom_size::Int; kw...) where {T,T′}
dom_size == 0 || error("Cannot specify $ob component with $f with $dom_size domain variables")
coerce_attrvar_component(ob, LooseVarFunction{T,T′}([], f, FinSet(codom_size)),
d, cd, dom_size,codom_size)
Expand Down Expand Up @@ -606,21 +610,16 @@ Xₕ ↓ ✓ ↓ Yₕ
You're allowed to run this on a named tuple partly specifying an ACSetTransformation,
though at this time the domain and codomain must be fully specified ACSets.
`only_combinatorial=true` means to only test naturality in combinatorial data
"""
function is_natural::LooseACSetTransformation; only_combinatorial=false)
is_natural(dom(α),codom(α),α.components,type_components(α); only_combinatorial)
function is_natural::LooseACSetTransformation)
is_natural(dom(α), codom(α), α.components, type_components(α))
end
function is_natural::ACSetTransformation; only_combinatorial=false)
is_natural(dom(α),codom(α),α.components; only_combinatorial)
end
function is_natural::CSetTransformation; only_combinatorial=true)
is_natural(dom(α),codom(α),α.components; only_combinatorial)
function is_natural::ACSetMorphism)
is_natural(dom(α), codom(α), α.components)
end

is_natural(dom,codom,comps...; only_combinatorial=false) =
all(isempty, last.(collect(naturality_failures(dom, codom, comps...; only_combinatorial))))
is_natural(dom, codom, comps...) =
all(isempty, last.(collect(naturality_failures(dom, codom, comps...))))

"""
Returns a dictionary whose keys are contained in the names in `arrows(S)`
Expand All @@ -629,32 +628,29 @@ over the elements of X(c) on which α's naturality square
for f does not commute. Components should be a NamedTuple or Dictionary
with keys contained in the names of S's morphisms and values vectors or dicts
defining partial functions from X(c) to Y(c).
`only_combinatorial=true` means to only look for naturality failures in combinatorial
data.
"""
function naturality_failures(X,Y,comps; only_combinatorial=false)
function naturality_failures(X, Y, comps)
type_comps = Dict(attr => SetFunction(identity, SetOb(X,attr), SetOb(X,attr))
for attr in attrtype(acset_schema(X)))
naturality_failures(X, Y, comps, type_comps; only_combinatorial)
naturality_failures(X, Y, comps, type_comps)
end
function naturality_failures(X, Y, comps, type_comps; only_combinatorial=false)
function naturality_failures(X, Y, comps, type_comps)
S = acset_schema(X)
Fun = Union{SetFunction,VarFunction,LooseVarFunction}
Fun = Union{SetFunction, VarFunction, LooseVarFunction}
comps = Dict(a => isa(comps[a],Fun) ? comps[a] : FinDomFunction(comps[a])
for a in keys(comps))

type_comps = Dict(a => isa(type_comps[a], Fun) ? type_comps[a] :
SetFunction(type_comps[a],TypeSet(X,a),TypeSet(Y,a))
for a in keys(type_comps))
α = merge(comps, only_combinatorial ? Dict() : type_comps)
arrs = [(f,c,d) for (f,c,d) in arrows(S) if haskey(α,c) && haskey(α,d)]
α(o::Symbol, i::AttrVar) = comps[o](i)
α(o::Symbol, i::Any) = o ob(S) ? comps[o](i) : type_comps[o](i)
ks = union(keys(comps), keys(type_comps))
arrs = filter(((f,c,d),) -> c ks && d ks, arrows(S))
ps = Iterators.map(arrs) do (f,c,d)
Xf,Yf,α_c,α_d = subpart(X,f),subpart(Y,f), α[c], α[d]
Pair(f,
Iterators.map(i->(i,Yf[α_c(i)],α_d(Xf[i])),
Iterators.filter(dom(α_c)) do i
Xf[i] in dom(α_d) && Yf[α_c(i)] != α_d(Xf[i])
Iterators.map(i->(i, Y[α(c, i), f], α(d, X[i, f])),
Iterators.filter(parts(X, c)) do i
Y[α(c,i), f] != α(d,X[i, f])
end))
end
Dict(ps)
Expand All @@ -664,8 +660,6 @@ naturality_failures(α::CSetTransformation) =
naturality_failures(dom(α), codom(α), α.components; combinatorial=true)
naturality_failures::TightACSetTransformation) =
naturality_failures(dom(α), codom(α), α.components)
naturality_failures::LooseACSetTransformation)=
naturality_failures(dom(α), codom(α), α.components, α.type_components)

""" Pretty-print failures of transformation to be natural.
Expand Down Expand Up @@ -1358,4 +1352,29 @@ function var_reference(X::ACSet, at::Symbol, i::Int)
error("Wandering variable $at#$p")
end

# Mark as deleted
#################

"""
Check whether an ACSetTransformation is still valid, despite possible deletion
of elements in the codomain. An ACSetTransformation that isn't in bounds will
throw an error, rather than return `false`, if run through `is_natural`.
"""
function in_bounds(f::ACSetTransformation)
X, Y = dom(f), codom(f)
S = acset_schema(X)
all(ob(S)) do o
all(parts(X, o)) do i
f[o](i) parts(Y, o)
end
end || return false
all(attrtypes(S)) do o
all(AttrVar.(parts(X, o))) do i
j = f[o](i)
!(j isa AttrVar) || j.val parts(Y, o)
end
end
end


end # module
22 changes: 14 additions & 8 deletions src/categorical_algebra/FinSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ using StaticArrays: StaticVector, SVector, SizedVector, similar_type
import Tables, PrettyTables

using ACSets
import ACSets.Columns: preimage
@reexport using ..Sets
using ...Theories, ...Graphs
using ..FinCats, ..FreeDiagrams, ..Limits, ..Subobjects
Expand Down Expand Up @@ -187,15 +188,22 @@ FinFunction(::typeof(identity), args...) =
FinFunction(f::AbstractDict, args...) =
FinFunctionDict(f, (FinSet(arg) for arg in args)...)

function FinFunction(f::AbstractVector{Int}, args...;
index=false, known_correct = false)
cod = FinSet(args[end])
function FinFunction(f::AbstractVector, args...;
index=false, known_correct = false,
dom_parts=nothing, codom_parts=nothing)
cod = FinSet(isnothing(codom_parts) ? args[end] : codom_parts)
f = Vector{Int}(f) # coerce empty vectors
if !known_correct
for (i,t) enumerate(f)
t cod || error("Value $t at index $i is not in $cod.")
if isnothing(dom_parts) || i dom_parts
t cod || error("Value $t at index $i is not in $cod.")
end
end
end
if !index
if !isnothing(dom_parts)
args = (length(f), args[2:end]...)
end
FinDomFunctionVector(f, (FinSet(arg) for arg in args)...)
else
index = index == true ? nothing : index
Expand Down Expand Up @@ -501,10 +509,8 @@ Sets.do_compose(f::Union{FinFunctionVector,IndexedFinFunctionVector},
FinDomFunctionVector(g.func[f.func], codom(g))

# These could be made to fail early if ever used in performance-critical areas
is_epic(f::FinFunction) =
length(codom(f)) == length(Set(values(collect(f))))
is_monic(f::FinFunction) =
length(dom(f)) == length(Set(values(collect(f))))
is_epic(f::FinFunction) = length(codom(f)) == length(Set(values(collect(f))))
is_monic(f::FinFunction) = length(dom(f)) == length(Set(values(collect(f))))

# Dict-based functions
#---------------------
Expand Down
12 changes: 6 additions & 6 deletions src/categorical_algebra/HomSearch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,18 +234,18 @@ function backtracking_search(f, X::ACSet, Y::ACSet;
end

pred_nt = NamedTuple{Ob}(let dic = get(predicates, c, Dict());
Union{Set{Int}, Nothing}[haskey(dic, p) ? Set(dic[p]) : nothing for p in parts(X,c)]
Union{Set{Int}, Nothing}[haskey(dic, p) ? Set(dic[p]) : nothing for p in 1:maxpart(X,c)]
end for c in Ob)

# Initialize state variables for search.
assignment = merge(
NamedTuple{Ob}(zeros(Int, nparts(X, c)) for c in Ob),
NamedTuple{Ob}(zeros(Int, maxpart(X, c)) for c in Ob),
NamedTuple{Attr}(Pair{Int,Union{AttrVar,attrtype_type(X,c)}}[
0 => AttrVar(0) for _ in parts(X,c)] for c in Attr)
0 => AttrVar(0) for _ in 1:maxpart(X,c)] for c in Attr)
)
assignment_depth = map(copy, assignment)
inv_assignment = NamedTuple{ObAttr}(
(c in monic ? zeros(Int, nparts(Y, c)) : nothing) for c in ObAttr)
(c in monic ? zeros(Int, maxpart(Y, c)) : nothing) for c in ObAttr)
loosefuns = NamedTuple{Attr}(
isnothing(type_components) ? identity : get(type_components, c, identity) for c in Attr)
state = BacktrackingState(assignment, assignment_depth,
Expand Down Expand Up @@ -305,8 +305,8 @@ function find_mrv_elem(state::BacktrackingState, depth)
S = acset_schema(state.dom)
mrv, mrv_elem = Inf, nothing
Y = state.codom
for c in ob(S), (x, y) in enumerate(state.assignment[c])
y == 0 || continue
for c in ob(S), x in parts(state.dom, c)
state.assignment[c][x] == 0 || continue
n = count(can_assign_elem(state, depth, c, x, y) for y in parts(Y, c))
if n < mrv
mrv, mrv_elem = n, (c, x)
Expand Down
35 changes: 32 additions & 3 deletions test/categorical_algebra/CSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -532,11 +532,20 @@ end
##########

const WG = WeightedGraph
A = @acset WG{Bool} begin V=1;E=2;Weight=2;src=1;tgt=1;weight=[AttrVar(1),true] end
B = @acset WG{Bool} begin V=1;E=2;Weight=1;src=1;tgt=1;weight=[true, false] end
A = @acset WG{Bool} begin V=1; E=2; Weight=2;
src=1; tgt=1; weight=[AttrVar(1), true]
end
B = @acset WG{Bool} begin V=1; E=2; Weight=1;
src=1; tgt=1; weight=[true, false]
end

@test VarFunction(A,:weight) == VarFunction{Bool}([AttrVar(1), true], FinSet(2))

f = ACSetTransformation(
Dict(:V=>[1],:E=>[1,2],:Weight=>[AttrVar(2), AttrVar(1)]), A, A
)
@test !is_natural(f) # this should not be true, bug in is_natural

f = ACSetTransformation(Dict(:V=>[1],:E=>[2,1],:Weight=>[false, AttrVar(1)]), A,B)
@test is_natural(f)
@test force(id(A) f) == force(f) == force(f id(B))
Expand Down Expand Up @@ -761,10 +770,30 @@ end
@test is_isomorphic(apex(ABC),expected)

# 3. Apply commutative monoid to attrs
ABC = pullback(AC,BC; attrfun=(weight=prod,))
ABC = pullback(AC, BC; attrfun=(weight=prod,))
expected = @acset WeightedGraph{Float64} begin V=6; E=7;
src=[1,1,2,3,3,4,5]; tgt=[2,3,4,4,5,6,6]; weight=[-5,-2,-2,-5,-3,-3,-5]
end
@test is_isomorphic(apex(ABC),expected)

# Mark as deleted
#################

@acset_type AbsMADGraph(SchWeightedGraph, part_type=BitSetParts) <: AbstractGraph
const MADGraph = AbsMADGraph{Symbol}
p2, p3 = path_graph.(MADGraph, 3:4)
p2[:weight] = [:y,AttrVar(add_part!(p2, :Weight))]
p3[:weight] = [AttrVar(add_part!(p3, :Weight)), :y, AttrVar(add_part!(p3, :Weight))]
f = homomorphism(p2, p3)
@test in_bounds(f) && is_natural(f)
rem_part!(p3, :Weight, 2)
p3[3, :weight] = :z
@test !in_bounds(f)

f = homomorphism(p2, p3)
@test in_bounds(f) && is_natural(f)
rem_part!(p3, :E, 3)
rem_part!(p3, :V, 4)
@test !in_bounds(f)

end
12 changes: 12 additions & 0 deletions test/categorical_algebra/HomSearch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,16 @@ results = first(is_iso1) ? results : reverse(results)
exp = @acset WG begin V=3; E=1; src=1; tgt=2; weight=[false] end
@test first(first(maximum_common_subobject(g1, g2; abstract=false))) == exp

# Mark as deleted
#################
@acset_type AbsMADGraph(SchWeightedGraph, part_type=BitSetParts) <: AbstractGraph
const MADGraph = AbsMADGraph{Symbol}

v1, v2 = MADGraph.(1:2)
@test !is_isomorphic(v1,v2)
rem_part!(v2, :V, 1)
@test is_isomorphic(v1,v2)
@test is_isomorphic(v2,v1)


end # module

0 comments on commit f0680ef

Please sign in to comment.