diff --git a/src/ACSetInterface.jl b/src/ACSetInterface.jl index e78fda1..9dfaccf 100644 --- a/src/ACSetInterface.jl +++ b/src/ACSetInterface.jl @@ -1,10 +1,11 @@ module ACSetInterface -export ACSet, acset_schema, acset_name, dom_parts, subpart_type, +export ACSet, acset_schema, acset_name, dom_parts, codom_parts, subpart_type, nparts, maxpart, parts, has_part, has_subpart, subpart, incident, add_part!, add_parts!, set_subpart!, set_subparts!, clear_subpart!, rem_part!, rem_parts!, cascading_rem_part!, cascading_rem_parts!, gc!, copy_parts!, copy_parts_only!, disjoint_union, tables, pretty_tables, - @acset, constructor, PartsType, DenseParts, MarkAsDeleted, rem_free_vars! + @acset, constructor, undefined_subparts, PartsType, DenseParts, MarkAsDeleted, + rem_free_vars!, parts_type using MLStyle: @match using StaticArrays: StaticArray @@ -113,6 +114,15 @@ where X is the dom of the f in the schema """ function dom_parts end +""" +Get the parts of the codomain of a morphism in an acset + +dom_parts(acs, f) == parts(acs, Y) + +where Y is the codom of the f in the schema +""" +function codom_parts end + """ Get the type assigned to a subpart in an acset, i.e. @@ -391,6 +401,16 @@ Get a nullary callable which constructs an (empty) ACSet of the same type """ function constructor end +""" +Given a hom, find which parts in its domain are undefined. +""" +function undefined_subparts end + +""" +Get the type used to store parts IDs. +""" +parts_type(::ACSet{PT}) where {PT} = PT + # Pretty printing ################# diff --git a/src/DenseACSets.jl b/src/DenseACSets.jl index 5e7cc15..8488723 100644 --- a/src/DenseACSets.jl +++ b/src/DenseACSets.jl @@ -498,6 +498,14 @@ end parts(acs, @ct dom(s, f)) end +@inline ACSetInterface.codom_parts(acs::StructACSet{S}, f::Symbol) where {S} = _codom_parts(acs, Val{S}, Val{f}) +@inline ACSetInterface.codom_parts(acs::DynamicACSet, f::Symbol) = runtime(_codom_parts, acs, acs.schema, f) + +@ct_enable function _codom_parts(acs, @ct(S), @ct(f)) + @ct s = Schema(S) + parts(acs, @ct codom(s, f)) +end + @inline function ACSetInterface.incident(acs::SimpleACSet, part, f::Symbol) preimage(dom_parts(acs, f), acs.subparts[f], part) end @@ -644,6 +652,15 @@ end ACSetInterface.cascading_rem_parts!(acs::ACSet, type, parts) = delete_subobj!(acs, Dict(type=>parts)) +function ACSetInterface.undefined_subparts(acs::SimpleACSet{<:DenseParts}, f::Symbol) + findall([!haskey(acs.subparts[f],i) for i in dom_parts(acs,f)]) +end + +function ACSetInterface.undefined_subparts(acs::SimpleACSet{<:MarkAsDeleted}, f::Symbol) + codom_ids = codom_parts(acs,f) + findall([acs.subparts[f][i] ∉ codom_ids for i in dom_parts(acs,f)]) +end + # Copy Parts ############ diff --git a/test/ACSets.jl b/test/ACSets.jl index 9e0d581..18301e8 100644 --- a/test/ACSets.jl +++ b/test/ACSets.jl @@ -73,7 +73,10 @@ for dds_maker in dds_makers view(dds,:Φ) isa ColumnView # Deletion. + @test_throws ArgumentError undefined_subparts(dds, :X) + @test undefined_subparts(dds, :Φ) == [] rem_part!(dds, :X, 2) + @test undefined_subparts(dds, :Φ) == [1] @test nparts(dds, :X) == 2 @test incident(dds, 1, :Φ) == [] if dds.parts[:X] isa IntParts @@ -583,50 +586,74 @@ RecSch = BasicSchema( [],[] ) +# DenseParts @acset_type RecDataInj(RecSch, index=[:src,:tgt], unique_index=[:thing]) @acset_type RecDataIdx(RecSch, index=[:src,:tgt,:thing]) @acset_type RecDataNoIdx(RecSch) -datainj = @acset RecDataInj begin - Thing=3 - Node=3 - Edge=3 - thing=[1,2,3] - src=[1,1,2] - tgt=[1,2,3] -end - -dataidx = @acset RecDataIdx begin - Thing=3 - Node=3 - Edge=3 - thing=[1,2,3] - src=[1,1,2] - tgt=[1,2,3] -end +recdata_makers = [ + RecDataInj, + RecDataIdx, + RecDataNoIdx, + () -> DynamicACSet("RecData", RecSch; index=[:src,:tgt]), + () -> AnonACSet(RecSch; index=[:src,:tgt]) +] -datanoidx = @acset RecDataNoIdx begin - Thing=3 - Node=3 - Edge=3 - thing=[1,2,3] - src=[1,1,2] - tgt=[1,2,3] +for recdata in recdata_makers + mydata = recdata() + add_parts!(mydata, :Node, 3) + add_parts!(mydata, :Thing, 3, thing=[1,2,3]) + add_parts!(mydata, :Edge, 3, src=[1,1,2], tgt=[1,2,3]) + + @test parts_type(mydata) <: DenseParts + + new2old = cascading_rem_parts!(mydata, :Node, 1) + + @test length(new2old[:Thing]) == nparts(mydata,:Thing) + @test nparts(mydata,:Thing) == 2 + @test length(new2old[:Node]) == nparts(mydata,:Node) + @test nparts(mydata,:Node) == 2 + @test length(new2old[:Edge]) == nparts(mydata,:Edge) + @test nparts(mydata,:Edge) == 1 + @test incident(mydata, 3, :thing) == [] + @test incident(mydata, 3, :src) == [] + @test incident(mydata, 3, :tgt) == [] end -map_inj = cascading_rem_parts!(datainj, :Node, 1) -map_idx = cascading_rem_parts!(dataidx, :Node, 1) -map_noidx = cascading_rem_parts!(datanoidx, :Node, 1) - -@test map_inj == map_idx -@test map_idx == map_noidx +# MarkAsDeleted parts +@acset_type RecDataInjMarkDel(RecSch, index=[:src,:tgt], unique_index=[:thing], part_type=BitSetParts) +@acset_type RecDataIdxMarkDel(RecSch, index=[:src,:tgt,:thing], part_type=BitSetParts) +@acset_type RecDataNoIdxMarkDel(RecSch, part_type=BitSetParts) + +recdata_makers = [ + RecDataInjMarkDel, + RecDataIdxMarkDel, + RecDataNoIdxMarkDel, + () -> DynamicACSet("RecData", RecSch; index=[:src,:tgt], part_type=MarkAsDeleted), + () -> AnonACSet(RecSch; index=[:src,:tgt], part_type=MarkAsDeleted) +] -@test nparts(datainj,:Thing) == 2 -@test nparts(datainj,:Node) == 2 -@test nparts(datainj,:Edge) == 1 -@test incident(datainj, 3, :thing) == [] -@test incident(datainj, 3, :src) == [] -@test incident(datainj, 3, :tgt) == [] +for recdata in recdata_makers + mydata = recdata() + add_parts!(mydata, :Node, 3) + add_parts!(mydata, :Thing, 3, thing=[1,2,3]) + add_parts!(mydata, :Edge, 3, src=[1,1,2], tgt=[1,2,3]) + + @test parts_type(mydata) <: MarkAsDeleted + + new2old = cascading_rem_parts!(mydata, :Node, 1) + + @test length(new2old[:Thing]) == nparts(mydata,:Thing) + @test nparts(mydata,:Thing) == 2 + @test length(new2old[:Node]) == nparts(mydata,:Node) + @test nparts(mydata,:Node) == 2 + @test length(new2old[:Edge]) == nparts(mydata,:Edge) + @test nparts(mydata,:Edge) == 1 + # IDs are not updated in MarkAsDeleted, so "1" is the deleted element + @test incident(mydata, 1, :thing) == [] + @test incident(mydata, 1, :src) == [] + @test incident(mydata, 1, :tgt) == [] +end # attributes and an injective index RecAttrSch = BasicSchema( @@ -648,9 +675,7 @@ dataattr = @acset RecAttrData{String,Symbol,Float64} begin attr3=[10.0,11.0,12.0] end -map_attr = cascading_rem_parts!(dataattr, :Node, 1) - -@test map_inj == map_attr +cascading_rem_parts!(dataattr, :Node, 1) @test all(map(x -> x ∈ subpart(dataattr,:attr1), ["2","3"])) @test only(subpart(dataattr,:attr2)) == :c