@@ -10,7 +10,8 @@ export ACSetHomomorphismAlgorithm, BacktrackingSearch, HomomorphismQuery,
1010 homomorphism, homomorphisms, is_homomorphic,
1111 isomorphism, isomorphisms, is_isomorphic,
1212 @acset_transformation , @acset_transformations ,
13- subobject_graph, partial_overlaps, maximum_common_subobject
13+ subobject_graph, partial_overlaps, maximum_common_subobject,
14+ debug_homomorphisms
1415
1516using ... Theories, .. CSets, .. FinSets, .. FreeDiagrams, .. Subobjects
1617using ... Graphs. BasicGraphs
@@ -20,7 +21,7 @@ using ACSets.DenseACSets: attrtype_type, delete_subobj!
2021using Random
2122using CompTime
2223using MLStyle: @match
23- using DataStructures: BinaryHeap, DefaultDict
24+ using DataStructures: BinaryHeap, DefaultDict, OrderedDict
2425
2526# Finding C-set transformations
2627# ##############################
@@ -85,7 +86,7 @@ homomorphism(X::ACSet, Y::ACSet; alg=BacktrackingSearch(), kw...) =
8586function homomorphism (X:: ACSet , Y:: ACSet , alg:: BacktrackingSearch ; kw... )
8687 result = nothing
8788 backtracking_search (X, Y; kw... ) do α
88- result = α ; return true
89+ result = get_hom (α) ; return true
8990 end
9091 result
9192end
@@ -101,11 +102,19 @@ homomorphisms(X::ACSet, Y::ACSet; alg=BacktrackingSearch(), kw...) =
101102function homomorphisms (X:: ACSet , Y:: ACSet , alg:: BacktrackingSearch ; kw... )
102103 results = []
103104 backtracking_search (X, Y; kw... ) do α
104- push! (results, map_components (deepcopy, α )); return false
105+ push! (results, map_components (deepcopy, get_hom (α) )); return false
105106 end
106107 results
107108end
108109
110+ function debug_homomorphisms (X:: ACSet , Y:: ACSet ; kw... )
111+ results = []
112+ m = backtracking_search (X, Y; debug= true , kw... ) do α
113+ push! (results, map_components (deepcopy, get_hom (α))); return false
114+ end
115+ results => m. debug
116+ end
117+
109118""" Is the first attributed ``C``-set homomorphic to the second?
110119
111120This function generally reduces to [`homomorphism`](@ref) but certain algorithms
@@ -152,6 +161,60 @@ is_isomorphic(X::ACSet, Y::ACSet, alg::BacktrackingSearch; kw...) =
152161# Backtracking search
153162# --------------------
154163
164+ """ Keep track of progress through backtracking homomorphism search."""
165+ mutable struct BacktrackingTree
166+ node:: Union{Nothing,Pair{Symbol,Int}}
167+ success:: Bool
168+ asgn:: NamedTuple
169+ children:: OrderedDict{Int,BacktrackingTree}
170+ BacktrackingTree () = new (nothing , false , (;), OrderedDict {Int,BacktrackingTree} ())
171+ end
172+
173+ """ A backtracking tree plus a pointer to a node in the tree"""
174+ struct BacktrackingTreePt
175+ t:: BacktrackingTree
176+ curr:: Vector{Int}
177+ BacktrackingTreePt () = new (BacktrackingTree (),Int[])
178+ end
179+
180+ function Base. push! (tc:: BacktrackingTreePt , c:: Symbol , x:: Int , y:: Int , asgn)
181+ t = tc. t[tc. curr]
182+ t. node = c => x
183+ t. children[y] = BacktrackingTree ()
184+ t. children[y]. asgn = deepcopy (asgn)
185+ push! (tc. curr, y)
186+ return true
187+ end
188+
189+ function Base. delete! (tc:: BacktrackingTreePt , c:: Symbol , x:: Int , y:: Int )
190+ t = tc. t[tc. curr[1 : end - 1 ]]
191+ t. node == (c=> x) || error (" Bad remove $c #$x ->$y " )
192+ pop! (tc. curr)
193+ end
194+
195+ function success (tc:: BacktrackingTreePt )
196+ tc. t[tc. curr]. success = true
197+ end
198+
199+ function Base. show (io:: IO , t:: BacktrackingTree )
200+ if ! isnothing (t. node)
201+ print (io," {" ); print (io, t. node[1 ]); print (io, t. node[2 ]); print (io," }" );
202+ end
203+ print (io, " [" )
204+ for (k,v) in collect (t. children)
205+ print (io, k); print (io, v); print (io, " ," )
206+ end
207+ if ! isempty (t. children) print (io," \b " ) end
208+ print (io," ]" )
209+ end
210+
211+ function Base. getindex (t:: BacktrackingTree , curr:: Vector{Int} )
212+ for c in curr
213+ t = t. children[c]
214+ end
215+ t
216+ end
217+
155218""" Get assignment pairs from partially specified component of C-set morphism.
156219"""
157220partial_assignments (x:: FinFunction ; is_attr= false ) = partial_assignments (collect (x))
@@ -177,10 +240,25 @@ struct BacktrackingState{
177240 dom:: Dom
178241 codom:: Codom
179242 type_components:: LooseFun
243+ debug:: Union{Nothing,BacktrackingTreePt}
244+ end
245+
246+ """ Extract an ACSetTransformation from BacktrackingState"""
247+ function get_hom (state:: BacktrackingState )
248+ if any (!= (identity), state. type_components)
249+ return LooseACSetTransformation (
250+ state. assignment, state. type_components, state. dom, state. codom)
251+ else
252+ S = acset_schema (state. dom)
253+ od = Dict {Symbol,Vector{Int}} (k=> (state. assignment[k]) for k in objects (S))
254+ ad = Dict (k=> last .(state. assignment[k]) for k in attrtypes (S))
255+ comps = merge (NamedTuple (od),NamedTuple (ad))
256+ return ACSetTransformation (comps, state. dom, state. codom)
257+ end
180258end
181259
182260function backtracking_search (f, X:: ACSet , Y:: ACSet ;
183- monic= false , iso= false , random= false ,
261+ monic= false , iso= false , random= false , debug = false ,
184262 type_components= (;), initial= (;), error_failures= false )
185263 S, Sy = acset_schema .([X,Y])
186264 S == Sy || error (" Schemas must match for morphism search" )
@@ -235,9 +313,11 @@ function backtracking_search(f, X::ACSet, Y::ACSet;
235313 inv_assignment = NamedTuple {ObAttr} (
236314 (c in monic ? zeros (Int, nparts (Y, c)) : nothing ) for c in ObAttr)
237315 loosefuns = NamedTuple {Attr} (
238- isnothing (type_components) ? identity : get (type_components, c, identity) for c in Attr)
239- state = BacktrackingState (assignment, assignment_depth,
240- inv_assignment, X, Y, loosefuns)
316+ isnothing (type_components) ? identity : get (type_components, c, identity)
317+ for c in Attr)
318+
319+ state = BacktrackingState (assignment, assignment_depth, inv_assignment, X, Y,
320+ loosefuns, debug ? BacktrackingTreePt () : nothing )
241321
242322 # Make any initial assignments, failing immediately if inconsistent.
243323 for (c, c_assignments) in pairs (initial)
@@ -252,39 +332,32 @@ function backtracking_search(f, X::ACSet, Y::ACSet;
252332 end
253333 end
254334 # Start the main recursion for backtracking search.
255- backtracking_search (f, state, 1 ; random= random)
335+ backtracking_search (f, state, 1 ; random= random, toplevel = true )
256336end
257337
258338function backtracking_search (f, state:: BacktrackingState , depth:: Int ;
259- random= false )
339+ random= false , toplevel = false )
260340 # Choose the next unassigned element.
261341 mrv, mrv_elem = find_mrv_elem (state, depth)
262342 if isnothing (mrv_elem)
263- # No unassigned elements remain, so we have a complete assignment.
264- if any (!= (identity), state. type_components)
265- return f (LooseACSetTransformation (
266- state. assignment, state. type_components, state. dom, state. codom))
267- else
268- S = acset_schema (state. dom)
269- od = Dict {Symbol,Vector{Int}} (k=> (state. assignment[k]) for k in objects (S))
270- ad = Dict (k=> last .(state. assignment[k]) for k in attrtypes (S))
271- comps = merge (NamedTuple (od),NamedTuple (ad))
272- return f (ACSetTransformation (comps, state. dom, state. codom))
273- end
343+ isnothing (state. debug) || success (state. debug)
344+ return f (state)
274345 elseif mrv == 0
275346 # An element has no allowable assignment, so we must backtrack.
276347 return false
277348 end
278- c, x = mrv_elem
349+ c, x, ys = mrv_elem
279350
280351 # Attempt all assignments of the chosen element.
281352 Y = state. codom
282- for y in (random ? shuffle : identity)(parts (Y, c) )
353+ for y in (random ? shuffle : identity)(ys )
283354 (assign_elem! (state, depth, c, x, y)
355+ && (isnothing (state. debug) ? true : push! (state. debug, c, x, y, state. assignment))
284356 && backtracking_search (f, state, depth + 1 )) && return true
285357 unassign_elem! (state, depth, c, x)
358+ isnothing (state. debug) || delete! (state. debug, c, x, state. assignment[c][x])
286359 end
287- return false
360+ return toplevel ? state : false # return state to recover debug tree
288361end
289362
290363""" Find an unassigned element having the minimum remaining values (MRV).
@@ -295,9 +368,12 @@ function find_mrv_elem(state::BacktrackingState, depth)
295368 Y = state. codom
296369 for c in ob (S), (x, y) in enumerate (state. assignment[c])
297370 y == 0 || continue
298- n = count (can_assign_elem (state, depth, c, x, y) for y in parts (Y, c))
371+ ys = filter (parts (Y,c)) do y
372+ can_assign_elem (state, depth, c, x, y)
373+ end
374+ n = length (ys)
299375 if n < mrv
300- mrv, mrv_elem = n, (c, x)
376+ mrv, mrv_elem = n, (c, x, ys )
301377 end
302378 end
303379 (mrv, mrv_elem)
0 commit comments