Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file cycles.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440typeweight=|Normalofint|ObligatoryofintmoduleFashwo(GB:sigincludeBuilder.Svalweight:G.edge->weightend)=structmoduleG=GB.GexceptionStuckofG.vertexlistmoduleIM=Map.Make(structtypet=intletcompare=Stdlib.compareend)moduleVM=Map.Make(G.V)moduleVS=Set.Make(G.V)(* The algorithm of Eades, Lin, and Smyth (ELS 1993) works by "scheduling"
vertexes onto two lists called s1 and s2. At each iteration a vertex is
chosen, scheduled, and removed from the graph. Arcs from a newly scheduled
node toward nodes already in s1 are classified as "leftward"; they are
included in the generated feedback arc set. "Rightward" arcs, to vertexes
in s2 or that have not yet been scheduled, are not included in the
feedback arc set. The algorithm tries to maximize the number of rightward
arcs and thereby minimize the number of leftward ones. Source vertexes,
those with no incoming arcs in the current graph (i.e., because all its
predecssors have already been scheduled), are appended directly onto s1
and do not induce any feedback arcs. Sink vertexes are consed directly
onto s2 and do not induce any feedback arcs. Otherwise, the algorithm
chooses a vertex to maximize the difference between the number of
outgoing arcs and the number of incoming ones: the (remaining) incoming
arcs must be included in the feedback arc set. The difference between the
number of rightward arcs (no cost) and the number of leftward arcs
(feedback arcs) is called "delta". The algorithm is implemented
efficiently by using a data structure to group unscheduled vertexes
according to their delta value. When more than one vertex has the maximum
delta value, the original algorithm makes an arbitrary choice. The
algorithm of Eades and Lin (EL 1995) makes the choice using a heuristic
that maximizes the difference between incoming arcs and outgoing ones in
the vertexes that remain at the end of the iteration as such vertexes are
the most "unbalanced" and thus less likely to contribute to the feedback
arc set in future iterations. The EL 1995 algorithm includes a further
refinement to ignore chains of vertexes when looking for unbalanced ones,
since such chains do not contribute feedback arcs.
Since we just want to produce a list of feedback arcs, we don't bother
tracking order in s1, and we only track s2 to properly handle the
preprocessing optimization that removes two cycles. We maintain lists of
source and sink vertexes (scheduled but not yet removed from the graph)
and a map from delta values to sets of vertexes. As the delta value map
caches the state of the graph, it must be updated when the a vertex is
scheduled and removed from the graph. Additionally, we remember which two
cycles were removed during preprocessing and ensure that one of their
arcs is included in the feedback arc set, depending on whichever of the
two interlinked vertexes is scheduled first. *)typet={s1:VS.t;(* vertexes placed "at left" *)s2:VS.t;(* vertexes placed "at right";
only needed to optimize for two_cycles *)sources:VS.t;(* vertexes with no incoming arcs *)sinks:VS.t;(* vertexes with no outgoing arcs *)delta_bins:VS.tIM.t;(* group vertexes by delta value *)vertex_bin:intVM.t;(* map each vertex to its bin *)two_cycles:G.edgelistVM.t;(* edges for 2-cycles *)fas:G.edgelist;(* current feedback arc set *)}letempty={s1=VS.empty;s2=VS.empty;sources=VS.empty;sinks=VS.empty;delta_bins=IM.empty;vertex_bin=VM.empty;two_cycles=VM.empty;fas=[];}letadd_to_bindeltav({delta_bins;vertex_bin;_}asst)={stwithdelta_bins=IM.updatedelta(functionNone->Some(VS.singletonv)|Somevs->Some(VS.addvvs))delta_bins;vertex_bin=VM.addvdeltavertex_bin}letremove_from_binv({delta_bins;vertex_bin;_}asst)=matchVM.find_optvvertex_binwith|None->st|Somedelta->{stwithdelta_bins=IM.updatedelta(functionNone->None|Somevs->Some(VS.removevvs))delta_bins;vertex_bin=VM.removevvertex_bin}(* Calculate the sums of incoming and outgoing edge weights, ignoring
obligatory arcs; they must be respected so their weight is irrelevant. *)letweightsgv=letadd_pweighte(s,b)=matchGB.weightewithObligatory_->(s,true)|Normalw->(s+w,b)inletadd_sweightes=matchGB.weightewithObligatoryw->s+w|Normalw->s+winletinw,blocked=G.fold_pred_eadd_pweightgv(0,false)inletoutw=G.fold_succ_eadd_sweightgv0inblocked,inw,outwletadd_vertexgvdelta({sources;sinks;_}asst)=letind,outd=G.in_degreegv,G.out_degreegvinifind=0then{stwithsources=VS.addvsources}elseifoutd=0then{stwithsinks=VS.addvsinks}elseadd_to_bindeltavst(* Initialize the state for a given vertex. *)letinit_vertexgvst=letblocked,inw,outw=weightsgvinifblockedthenstelseadd_vertexgv(outw-inw)stletinitg=G.fold_vertex(init_vertexg)gempty(* Move v from the bin for delta to sources, sinks, or another bin. *)letshift_binsgvdelta'st0=add_vertexgvdelta'(remove_from_binvst0)(* Before removing v from the graph, update the state of its sucessors. *)letupdate_removed_succg'est=letv=G.E.dsteinletstill_blocked,inw',outw'=weightsg'vinifstill_blockedthenstelseshift_binsg'v(outw'-inw')st(* Before removing v from the graph, update the state of its predecessors. *)letupdate_removed_predg'e({sinks;_}asst)=letv=G.E.srceinletblocked,inw',outw'=weightsg'vinmatchGB.weightewith|Obligatory_->ifblocked||outw'>0thenstelse(* not blocked && outw' = 0 *){(remove_from_binvst)withsinks=VS.addvsinks}|Normal_->ifblockedthenstelseshift_binsg'v(outw'-inw')st(* Remove a vertex from the graph and update the data structures for its
succesors and predecessors. *)letremove_vertexgvst=letg'=GB.remove_vertexgvin(g',G.fold_succ_e(update_removed_succg')gvst|>G.fold_pred_e(update_removed_predg')gv)(* The original article proposes preprocessing the graph to condense long
chains of vertexes. This works together with the heuristic for generating
unbalanced vertexes, since the intermediate nodes on the chain do not
contribute any leftward arcs (when the last vertex is removed, they
become a sequence of sinks). Using such a preprocessing step with
weighted edges risks removing good feedback arcs, i.e., those with a big
difference between outgoing and incoming weights. That is why here we
use on-the-fly condensation, even if there is a risk of recomputing the
same result several times. *)letreccondensewgv=ifG.out_degreegv=1thenmatchG.predgvwith|[u]whennot(G.V.equaluw)->condensewgu|_->velsev(* Find the vertex v that has the most "unbalanced" predecessor u. Most
unbalanced means the biggest difference between the input weights and
output weights. Skip any vertex with an incoming obligatory arc. *)lettakemaxgvimax=letcheck_edgeemax=(* check u -> v *)letu_blocked,u_inw,u_outw=weightsg(condense(G.E.dste)g(G.E.srce))inletu_w=u_inw-u_outwinmatchmaxwith|Some(None,_)|None->Some((ifu_blockedthenNoneelseSomeu_w),v)|Some(Somex_w,_)whenu_w>x_w->Some(Someu_w,v)|_->maxinG.fold_pred_echeck_edgegvimax(* Look for the vertex with the highest delta value that is not the target
of an obligatory arc. Use the "unbalanced" heuristic impllemented in
[takemax] to discriminate between competing possibilities. If a vertex
is found, remove it from the returned delta bins. *)(*
let max_from_deltas g ({ delta_bins; _ } as st) =
let rec f = function
| Seq.Nil -> None
| Seq.Cons ((_, dbin), tl) ->
(match VS.fold (takemax g) dbin None with
| None -> f (tl ())
| Some (_, v) -> Some (v, remove_from_bin v st))
in
f (IM.to_rev_seq delta_bins ())
*)letmax_from_deltasg({delta_bins;_}asst)=letrecfim=ifIM.is_emptyimthenNoneelseletk,dbin=IM.max_bindingimin(matchVS.fold(takemaxg)dbinNonewith|None->f(IM.removekim)|Some(_,v)->Some(v,remove_from_binvst))infdelta_bins(* Include any leftward arcs due to the two-cycles that were removed by
preprocessing. *)letadd_from_two_cycless1s2two_cyclesvfas=letbfesb=ifG.V.equal(G.E.dstb)vthenb::eselseesinletfese=letw=G.E.dsteinifVS.memws1thene::eselseifVS.memws2then(* the two-cycle partner has already been scheduled as sink, so
the feedback edges come from it. *)matchVM.find_optwtwo_cycleswith|None->es|Somebs->List.fold_leftbfesbselseesinmatchVM.find_optvtwo_cycleswith|None->fas|Somees->List.fold_leftffases(* Shift a given vertex onto s1, and add any leftward arcs to the feedback
arc set. *)letschedule_vertexg(v,({s1;s2;fas;two_cycles;_}asst))=letadd_to_fasees=ifVS.mem(G.E.srce)s1theneselsee::esin(v,{stwiths1=VS.addvs1;fas=G.fold_pred_eadd_to_fasgvfas|>add_from_two_cycless1s2two_cyclesv})(* Take the next available vertex from, in order, sources, sinks, or the
highset possible delta bin. *)letchoose_vertexg({s1;s2;sources;sinks;two_cycles;fas;_}asst0)=matchVS.choose_optsourceswith|Somev->Some(v,{st0withsources=VS.removevsources;sinks=VS.removevsinks;s1=VS.addvs1;fas=add_from_two_cycless1s2two_cyclesvfas})|None->(matchVS.choose_optsinkswith|Somev->Some(v,{st0withsinks=VS.removevsinks;s2=VS.addvs2;fas=add_from_two_cycless1s2two_cyclesvfas})|None->Option.map(schedule_vertexg)(max_from_deltasgst0))letadd_two_cycle_edgetwo_cyclese=VM.update(G.E.srce)(functionNone->Some[e]|Somees->Some(e::es))two_cyclesletsame_weightwe=matchGB.weightewith|Obligatory_->false|Normalw'->w'=w(* For every pair of distinct vertexes A and B linked to each other by
edges A -ab-> B and B -ba-> A with the same weight, update the mapping
by linking A to ab, and B to ba, and remove the edges from the graph.
When A is scheduled, if B is already in s1 then the edge ab is a
feedback arc, and similarly for B and ba. The principle is that there
will be a feedback arc regardless of whether A is "scheduled" before B or
vice versa, therefore such cycles should not constrain vertex choices. *)letremove_two_cyclesg0=letfe((g,cycles)asunchanged)=matchGB.weightewith|Obligatory_->unchanged|Normalw->ifList.length(G.find_all_edgesg0(G.E.srce)(G.E.dste))>1(* invalid for graphs like: { A -1-> B, A -2-> B, B -3-> A *)thenraiseExitelseletback_edges=G.find_all_edgesg0(G.E.dste)(G.E.srce)|>List.filter(same_weightw)inifback_edges=[]thenunchangedelse(GB.remove_edge_ege,List.fold_leftadd_two_cycle_edgecyclesback_edges)intryG.fold_edges_efg0(g0,VM.empty)withExit->(g0,VM.empty)(* All self loops must be broken, so just add them straight into the
feedback arc set. *)letremove_self_loopsg0=letfv(g,fas)=letself_loops=G.find_all_edgesg0vvin(List.fold_leftGB.remove_edge_egself_loops,List.rev_appendself_loopsfas)inG.fold_vertexfg0(g0,[])(* Remove any arcs between strongly connected components. There can be no
cycles between distinct sccs by definition. *)moduleC=Components.Make(G)moduleEmap=Gmap.Edge(G)(structincludeGB.GincludeGBend)letdisconnect_sccsg=letnsccs,fscc=C.sccginletin_same_scce=iffscc(G.E.srce)=fscc(G.E.dste)thenSomeeelseNoneinifnsccs<2thengelseEmap.filter_mapin_same_sccgletfeedback_arc_setg0=letrecloop(g,st)=matchchoose_vertexgstwith|Some(v,st')whenG.mem_vertexgv->loop(remove_vertexgvst')|Some(_,st')->loop(g,st')|None->letremaining=IM.fold(Fun.constVS.union)st.delta_binsVS.emptyinifVS.is_emptyremainingthenst.faselseraise(Stuck(VS.elementsremaining))inletg1=disconnect_sccsg0inletg2,fas=remove_self_loopsg1inletg3,two_cycles=remove_two_cyclesg2inloop(g3,{(initg3)withfas;two_cycles})endmoduletypeG=sigtypetmoduleV:Sig.COMPARABLEvalnb_vertex:t->intvaliter_vertex:(V.t->unit)->t->unitvaliter_succ:(V.t->unit)->t->V.t->unitvalfold_succ:(V.t->'a->'a)->t->V.t->'a->'aendmoduleJohnson(G:G):sigvaliter_cycles:(G.V.tlist->unit)->G.t->unitvalfold_cycles:(G.V.tlist->'a->'a)->G.t->'a->'aend=structmoduleVMap=structmoduleVH=Hashtbl.Make(G.V)letcreate=VH.createletfind=VH.findletadd=VH.addletiter=VH.iterend(* The algorithm visits each vertex.
For each vertex, it does a depth-first search to find all paths back to
the same vertex. *)typevinfo={(* records whether the vertex has been visited *)mutablevisited:bool;(* [blocked] and [blist are used to avoid uselessly iterating over a
subgraph from which a cycle can (no longer) be found. *)mutableblocked:bool;mutableblist:G.V.tlist;}(* map each vertex to the information above *)letfindtblkey=tryVMap.findtblkeywithNot_found->letinfo={visited=false;blocked=false;blist=[]}inVMap.addtblkeyinfo;infoletiter_cyclesf_cycleg=letinfo=VMap.create(G.nb_vertexg)in(* recursively unblock a subgraph *)letrecunblockvi=ifvi.blockedthenbeginvi.blocked<-false;List.iter(funw->unblock(findinfow))vi.blist;vi.blist<-[];endinletcycles_from_vertexs=letreccircuitpathvvi=letcheck_succwcycle_found=ifG.V.equalws(* a cycle is found *)then(f_cyclepath;true)else(* keep looking *)letwi=findinfowinifnot(wi.blocked||wi.visited)thencircuit(w::path)wwielsecycle_foundin(* v should be unblocked if any of its successors are, since a cycle
from v may be found via a newly unblocked successor. *)letunblock_onw=letwi=findinfowinwi.blist<-v::wi.blistin(* not (yet) interested in cycles back to v that do not pass via s *)vi.blocked<-true;ifG.fold_succcheck_succgvfalse(* DFS on successors *)(* if we found a cycle through v then unblock it *)then(unblockvi;true)(* otherwise there's no reason to try again unless something changes *)else(G.iter_succunblock_ongv;false)inVMap.iter(fun_info->info.blocked<-false;info.blist<-[])info;letsi=findinfosin(* look for elementary cycles back to s *)ignore(circuit[s]ssi);si.visited<-trueinG.iter_vertexcycles_from_vertexgletfold_cyclesfgi=letacc=refiiniter_cycles(funcycle->acc:=fcycle!acc)g;!accend