From 8a786b99649f1d9986986d2db91790322b3abbcd Mon Sep 17 00:00:00 2001 From: Kris Brown Date: Mon, 22 Jan 2024 13:50:06 -0800 Subject: [PATCH] mark as deleted basic compatibility in_bounds, fix is_natural, homsearch generic to dense/sparse columns are used for sparse hom search as well as when initiating an ACSetTransformation with Dicts is_monic for columns sparsify, densify, trying to make columns behave like finfunctions use FinFunctions even for MarkAsDeleted tests are broken more updates hom search and acset transformations for mark as deleted rem --- src/categorical_algebra/ACSetsGATsInterop.jl | 5 +- src/categorical_algebra/CSets.jl | 85 +++++++++++++------- src/categorical_algebra/FinSets.jl | 22 +++-- src/categorical_algebra/HomSearch.jl | 10 +-- test/categorical_algebra/CSets.jl | 35 +++++++- test/categorical_algebra/HomSearch.jl | 12 +++ 6 files changed, 122 insertions(+), 47 deletions(-) diff --git a/src/categorical_algebra/ACSetsGATsInterop.jl b/src/categorical_algebra/ACSetsGATsInterop.jl index 9f739b977..33631964f 100644 --- a/src/categorical_algebra/ACSetsGATsInterop.jl +++ b/src/categorical_algebra/ACSetsGATsInterop.jl @@ -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( diff --git a/src/categorical_algebra/CSets.jl b/src/categorical_algebra/CSets.jl index e52674155..363ccdd94 100644 --- a/src/categorical_algebra/CSets.jl +++ b/src/categorical_algebra/CSets.jl @@ -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 @@ -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 @@ -427,31 +427,35 @@ 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)) @@ -459,7 +463,7 @@ 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 @@ -467,7 +471,7 @@ 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) @@ -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) @@ -630,8 +634,8 @@ 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. +`only_combinatorial=true` means to only look for naturality failures in +combinatorial data. """ function naturality_failures(X,Y,comps; only_combinatorial=false) type_comps = Dict(attr => SetFunction(identity, SetOb(X,attr), SetOb(X,attr)) @@ -643,18 +647,18 @@ function naturality_failures(X, Y, comps, type_comps; only_combinatorial=false) 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) @@ -664,8 +668,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. @@ -1357,4 +1359,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 diff --git a/src/categorical_algebra/FinSets.jl b/src/categorical_algebra/FinSets.jl index 90f0074a8..fb6f5cc5c 100644 --- a/src/categorical_algebra/FinSets.jl +++ b/src/categorical_algebra/FinSets.jl @@ -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 @@ -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 @@ -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 #--------------------- diff --git a/src/categorical_algebra/HomSearch.jl b/src/categorical_algebra/HomSearch.jl index f27a50eb1..c10a8a79b 100644 --- a/src/categorical_algebra/HomSearch.jl +++ b/src/categorical_algebra/HomSearch.jl @@ -239,13 +239,13 @@ function backtracking_search(f, X::ACSet, Y::ACSet; # 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, @@ -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) diff --git a/test/categorical_algebra/CSets.jl b/test/categorical_algebra/CSets.jl index 6c1d09242..6210dc26a 100644 --- a/test/categorical_algebra/CSets.jl +++ b/test/categorical_algebra/CSets.jl @@ -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)) @@ -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 diff --git a/test/categorical_algebra/HomSearch.jl b/test/categorical_algebra/HomSearch.jl index 295e2cc89..c20e549a9 100644 --- a/test/categorical_algebra/HomSearch.jl +++ b/test/categorical_algebra/HomSearch.jl @@ -213,4 +213,16 @@ results = first(is_iso1) ? results : reverse(results) @test collect(R2[:V]) == [3,1,2] @test L2(apx2) == Subobject(g1, V=[1,2,3], E=[1,3]) +# 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