123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264includeIncremental_cycles_intfmoduleMake(Raw_graph:Raw_graph):Swithtypegraph:=Raw_graph.graphandtypevertex:=Raw_graph.vertex=struct(* This implements the algorithm of incremental cycle detection described in
Section 2 of the following paper:
A New Approach to Incremental Cycle Detection and Related Problems
Bender, M. A., Fineman, J. T., Gilbert, S., & Tarjan, R. E. (2015).
https://dl.acm.org/citation.cfm?id=2756553
*)(******************************************************************************)(* The implementation of the algorithm only depends on an abstract graph
structure, here implemented by [Raw_graph].
Here, [raw_graph.ml] provides a concrete implementation, but the proof
only relies on the abstract interface it implements. In the final exported
code (see the export/ directory), [Raw_graph] becomes a functor parameter.
*)openRaw_graph(******************************************************************************)(* Interruptible fold_left.
At each step, the client function decides whether it wants to continue (by
using [Continue new_accumulator]) or stop (by using [Break return_value]).
Ultimately, [interruptible_fold] returns either the last accumulator or the
value returned by [Break], along with a boolean indicating whether it was
interrupted prematurely.
*)type('a,'b)interruptible_fold_step=|Continueof'a|Breakof'bletrecinterruptible_foldflacc=matchlwith|[]->Continueacc|x::xs->letres=fxaccinmatchreswith|Continueacc->interruptible_foldfxsacc|Break_->res(******************************************************************************)(* The cycle detection algorithm, implemented as an [add_edge] function on
[graph], which either successfully inserts the edge, or reports a cycle. *)typevisit_backward_result=|VisitBackwardCompleted|VisitBackwardInterrupted|VisitBackwardCyclic(* Traverse the graph backwards from entries in [stack], looking for [target],
and marking explored vertices with [mark].
If a path to [target] is found, return [VisitBackwardCyclic].
If [fuel] runs out, return [VisitBackwardInterrupted].
Otherwise, report that the search was complete with [VisitBackwardCompleted].
*)letrecvisit_backward(g:graph)(target:vertex)(mark:mark)(fuel:int)(stack:vertexlist):visit_backward_result=(* fuel >= 0 *)matchstackwith|[]->VisitBackwardCompleted|vertex::stack->letres=interruptible_fold(funy(stack,fuel)->iffuel=0then(* There is no fuel left *)Breaktrueelseifis_markedgymarkthen(* This vertex has already been visited, skip it *)Continue(stack,fuel-1)elseifvertex_eqytargetthen(* A path to [target] has been found *)Breakfalseelsebeginset_markgymark;set_parentgyvertex;Continue(y::stack,fuel-1)end)(get_incominggvertex)(stack,fuel)inmatchreswith|Breaktimeout->iftimeoutthenVisitBackwardInterruptedelse(set_parentgtargetvertex;VisitBackwardCyclic)|Continue(stack,fuel)->visit_backwardgtargetmarkfuelstacktypebackward_search_result=|BackwardForwardofint*mark|BackwardCyclic|BackwardAcyclic(* The whole backwards search phase (Step 2 of the algorithm). Explores the
graph backwards starting from [v], and looking for [w].
This function mainly calls [visit_backward] and does some post-processing.
If [w] is found, return [BackwardCyclic].
If [w] is not found and the algorithm should continue with the forward
search phase, return [BackwardForward (new_w_level, visited)], where
[new_w_level] is the level at which [w] needs to be put, and [visited]
is the mark of vertices that have been visited during the search.
If [w] is not found and the algorithm should directly skip to the last step,
return [BackwardAcyclic].
*)letbackward_search(fuel:int)(g:graph)(v:vertex)(w:vertex):backward_search_result=letmark=new_markginletv_level=get_levelgvinset_markgvmark;matchvisit_backwardgwmarkfuel[v]with|VisitBackwardCyclic->BackwardCyclic|VisitBackwardInterrupted->(* w_level < v_level + 1 *)BackwardForward(v_level+1,mark)|VisitBackwardCompleted->letw_level=get_levelgwinifw_level=v_levelthenBackwardAcyclicelse(* w_level < v_level *)BackwardForward(v_level,mark)typeforward_search_result=|ForwardCyclicofvertex*vertex|ForwardCompleted(* Traverse the graph forwards. [stack] contains the current working set of
vertices; these are at level [new_level] but their neighbors have not been
yet all visited.
Only follow edges that point to vertices with a smaller level, but update the
incoming edges sets for all vertices encountered.
If a vertex that has been visited during the backward search phase is
encountered, return [ForwardCyclic]. Otherwise, return [ForwardCompleted]. *)letrecvisit_forward(g:graph)(new_level:int)(visited:mark)(stack:vertexlist):forward_search_result=matchstackwith|[]->ForwardCompleted|x::stack->letres=interruptible_fold(funystack->ifis_markedgyvisitedthen(* We found a path to a marked vertex *)Breakyelsebeginlety_level=get_levelgyinset_parentgyx;ify_level<new_levelthenbeginset_levelgynew_level;clear_incominggy;add_incominggyx;Continue(y::stack)endelseify_level=new_levelthenbeginadd_incominggyx;Continuestackendelse(* y_level > new_level *)Continuestackend)(get_outgoinggx)stackinmatchreswith|Breaky->ForwardCyclic(x,y)|Continuestack->visit_forwardgnew_levelvisitedstack(* The whole forward search phase (Step 3 of the algorithm). Explores the
graph forwards starting from [w], updating the levels and incoming edges
sets.
This function is a simple wrapper over [visit_forward].
*)letforward_search(g:graph)(w:vertex)(new_w_level:int)(visited:mark):forward_search_result=clear_incominggw;set_levelgwnew_w_level;visit_forwardgnew_w_levelvisited[w]typeadd_edge_result=|EdgeAdded|EdgeCreatesCycleof(unit->vertexlist)letreclist_of_parents(g:graph)(x:vertex)(y:vertex)(acc:vertexlist):vertexlist=ifvertex_eqxythenaccelseletp=get_parentgxinletacc'=p::accinifvertex_eqpythenacc'elselist_of_parentsgpyacc'(* (z, t) is an edge of the graph such that:
- z has been visited by the forward traversal
- t has been visited by the backward traversal
So the path from w to v is of the form:
w -> ... -> z -> t -> ... -> v
[compute_cycle] returns the list of nodes in that path (including w and v).
*)letcompute_cycle(g:graph)(v:vertex)(w:vertex)(z:vertex)(t:vertex)=list_of_parentsgzw(z::t::List.rev(list_of_parentsgtv[]))(* The core of the algorithm, wrapping up the previous phases.
This efficiently checks if there is a path from [w] to [v].
If there is none, then it adds the edge [(v, w)] to the graph. *)letadd_edge_or_detect_cycle(g:graph)(v:vertex)(w:vertex)=letsucceed()=raw_add_edgegvw;ifget_levelgv=get_levelgwthenadd_incominggwv;EdgeAddedinifvertex_eqvwthenEdgeCreatesCycle(fun()->[v])elseifget_levelgw>get_levelgvthen(* There cannot be a path from [w] to [v], as levels form a
pseudo-lexicographic ordering: edges always go to equal or increasing
levels. *)succeed()elsematchbackward_search(get_levelgv)gvwwith|BackwardCyclic->EdgeCreatesCycle(fun()->w::List.rev(list_of_parentsgwv[]))|BackwardAcyclic->succeed()|BackwardForward(new_level,visited)->matchforward_searchgwnew_levelvisitedwith|ForwardCyclic(z,t)->EdgeCreatesCycle(fun()->compute_cyclegvwzt)|ForwardCompleted->succeed()letadd_vertex(g:graph)(v:vertex)=raw_add_vertexgvend