123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692(**************************************************************************)(* *)(* Ocamlgraph: a generic graph library for OCaml *)(* Copyright (C) 2004-2010 *)(* Sylvain Conchon, Jean-Christophe Filliatre and Julien Signoles *)(* *)(* This software is free software; you can redistribute it and/or *)(* modify it under the terms of the GNU Library General Public *)(* License version 2.1, with the special exception on linking *)(* described in file LICENSE. *)(* *)(* This software is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *)(* *)(**************************************************************************)moduletypeFLOW=sigtypettypelabelvalmax_capacity:label->tvalflow:label->tvaladd:t->t->tvalsub:t->t->tvalzero:tvalcompare:t->t->intendmoduletypeG_GOLDBERG_TARJAN=sigtypetmoduleV:Sig.COMPARABLEmoduleE:Sig.EDGEwithtypevertex=V.tvalnb_vertex:t->intvalnb_edges:t->intvalfold_edges_e:(E.t->'a->'a)->t->'a->'avalfold_succ_e:(E.t->'a->'a)->t->V.t->'a->'avalfold_pred_e:(E.t->'a->'a)->t->V.t->'a->'aendmoduleGoldberg_Tarjan(G:G_GOLDBERG_TARJAN)(F:FLOWwithtypelabel=G.E.label)=struct(* This code is a contribution of Guyslain Naves
Design notes:
This is an implementation of the classical Goldberg-Tarjan push-relabel
algorithm to compute maximum flow in directed graphs with upper capacities
on arcs. Several common optimizations are implemented to make it
more efficient than the pseudocode found in most textbooks on algorithms.
About the push-relabel algorithm.
--------------------------------------
Instead of keeping a valid flow and improving it by iteration (similar to
Ford-Fulkerson algorithm and its variants), the push-relabel always keep
a preflow and try make it becoe a flow. A preflow is a function on arcs that
violates the flow condition:
"flow that enters = flow that leaves (in every non-terminal vertex)"
and replaces it by:
" flow that enters >= flow that leaves (in every non-source vertex)"
That means that any vertex may have *excessive* flow entering it.
The algorithm proceed by making flow going down. Here down is defined
by a *potential*, an integer attached to every vertex. The excess at some
vertex can be routed to a successor in the residual graph with lower
potential. This is a *push* operation on the corresponding arc.
Pushing along all arcs leaving a vertex is a *discharge* of this vertex.
If a vertex is excessive, but no successor has lower potential, then we
increase the potential to make it slightly higher than at least one
of its successor. This is a *relabel* operation of that vertex.
The source (potential = n) and sink (potential = 0) may never be relabel.
The algorithm consists in doing push and relabel steps,
until no more are possible. Then the preflow is a maximum flow.
Optimizations.
--------------
- The simplest (and less efficient) way to optimize this algorithm is
to play with the order on which push and relabel operations are performed.
Here, the strategy used is the following:
1) sort excessive vertices by decreasing potential
2) for each vertex in that order:
a) discharge it
b) if still in excess, relabel it
(see [augmenting_step] and [discharge])
This is a basic strategy that could be improved.
- Textbook algorithms starts with non-source vertices with potential 0.
This forces the algorithm to perform a lot of relabel operations to get
to a more realistic and usable potential function. Here we use as initial
potential the distance from a vertex to the sink (even for the source).
(see [initialize_potential])
- The most important optimization: empirically one can check that the
push-relabel algorithm converges very quickly toward a preflow that maximizes
the flow sent to the sink. But then it takes very long to send back the
excessive flow to the source. Here we detect every few iterations if the
preflow is maximal. This is done by a bfs in the reversal of the residual
graph, by determining whether the source is reachable.
(see [is_maximum_preflow]).
Once the preflow is maximum (first pahse), we compute a maximum preflow from
sink to source in the reversed graph, with maximum capacities given by the
values of the maximum preflow just computed (second phase). This will
compute a reversed maximum preflow that must be actually a flow.
(see [compute_maximum_flow], and the use of [forward_context]
and [backward_context] for each of the two phases)
Implementation.
---------------
The most important thing is to understand that we are interested only
in the residual graph, and not by the original graph. Original arcs
may appears in one or two directions in the residual graph. This is
why we manipulate almost only [residual_arc] and no [arc = G.E.t].
It also implies that the incident arcs of a vertex are not those in
the original graph. Because we will work with both the residual graph
and its reversal, and that it depends on the current flow values, we
define two functions [incidence_residual] and [incidence_reversal].
Notice that their roles interchanged during the second phase.
[Forward] and [Backward] only refers to the orientation compared to the
orientation in the graph. Hence, in a reversed graph, backward residual arcs
are [Forward], and forward residual arcs are [Backward].
We define a type [context] containing the current state of computation.
We hide the data structures in [context] by providing [set] and [get]
functions. It makes the code more readable, and easier to modify if one
would like to change the data structures.
Structure of the code:
The first part of the code contains mostly helpers, up to the bfs algorithm.
With the bfs comes the function to compute the initial potential and
check the maximality of a preflow.
Then we define the push, relabel, discharge operations, and the functions
computing maximal preflows.
Finally we define how to initialize a context, and the max flow algorithm.
We choose to require by a functor the implementations for the main data
structures used by the algorithm. VMap and EMap could be replaced by
arrays for better efficiency, or using vertex and edge labels.
They are used to record potentials, excesses and flows.
VSet is used to track vertices that are still in excess. Because
we sort those vertices, using search trees would not be a problem,
but an array of size [G.nb_vertex] could degrade the performance.
The default is a hash table densely filled, hence [sort_by_potential]
is the asymptotical bottleneck.
Parameter:
- [param_freq_check_preflow]: used to parametrized how often one should check
whether the current preflow is maximum.
*)typevertex=G.V.ttypearc=G.E.ttypeflow=F.tlet(|>)xf=fxopenGmoduleQ=PersistentQueuemoduleVH=Hashtbl.Make(G.V)moduleEM=Map.Make(G.E)moduleVMap=structtype'at='aVH.tletcreate=VH.createletaddtblkeyvalue=VH.addtblkeyvalueletremovetblkey=VH.removetblkeyletfindtblkey=trySome(VH.findtblkey)withNot_found->NoneendmoduleVSet=structtypet=unitVH.tletcreate()=VH.create16letaddtblv=ifnot(VH.memtblv)thenVH.addtblv()letelementstbl=VH.fold(funv()list->v::list)tbl[]endmoduleEMap=structtype'at='aEM.trefletcreate_=refEM.emptyletaddmapedgevalue=map:=EM.addedgevalue!mapletfindmapedge=trySome(EM.findedge!map)withNot_found->Noneendletmin_flowab=ifF.compareab<0thenaelseblet(+-)=F.addlet(--)=F.subletis_positivea=F.compareaF.zero>0letmax_capacitye=F.max_capacity(E.labele)typeresidual_arc=|Forwardofarc|Backwardofarc(* context for computations *)typecontext={nb_vertices:int;source:vertex;sink:vertex;reversed:bool;incident:context->vertex->residual_arclist;reverse_incident:context->vertex->residual_arclist;max_capacity:arc->F.t;excess:flowVMap.t;potential:intVMap.t;mutableexcessives:VSet.t;flow:flowEMap.t}letget_excessctxtvertex=matchVMap.findctxt.excessvertexwith|Somevalue->value|None->F.zeroletget_potentialctxtvertex=matchVMap.findctxt.potentialvertexwith|Somevalue->value|None->2*ctxt.nb_vertices(* sink is not reachable from vertex *)letset_excessctxtvertexvalue=VMap.removectxt.excessvertex;VMap.addctxt.excessvertexvalueletset_potentialctxtvertexpi=VMap.removectxt.potentialvertex;VMap.addctxt.potentialvertexpiletmark_excessivectxtvertex=VSet.addctxt.excessivesvertexletextract_excessivesctxt=letin_excess=VSet.elementsctxt.excessivesinctxt.excessives<-VSet.create();in_excessletget_flowcontextarc=matchEMap.findcontext.flowarcwith|Somevalue->value|None->F.zeroletset_flowcontextarcvalue=EMap.addcontext.flowarcvalueletget_capacitycontext=function|Backwardarc|Forwardarc->context.max_capacityarc(* residual graph helpers *)letorigin:residual_arc->vertex=function|Forwardarc->E.srcarc|Backwardarc->E.dstarcletdestination:residual_arc->vertex=function|Forwardarc->E.dstarc|Backwardarc->E.srcarcletforwardarc=Forwardarcletbackwardarc=Backwardarcletresidual_capacity:context->residual_arc->flow=funcontextresidual_arc->matchcontext.reversed,residual_arcwith|true,Forwardarc|false,Backwardarc->get_flowcontextarc|_,Backwardarc|_,Forwardarc->F.sub(context.max_capacityarc)(get_flowcontextarc)letis_forwardcontextarc=is_positive(context.max_capacityarc--get_flowcontextarc)letis_backwardcontextarc=is_positive(get_flowcontextarc)letaugment:context->residual_arc->F.t->unit=funcontextresidual_arcdelta->matchcontext.reversed,residual_arcwith|true,Backwardarc|false,Forwardarc->get_flowcontextarc+-delta|>set_flowcontextarc|_,Backwardarc|_,Forwardarc->get_flowcontextarc--delta|>set_flowcontextarcletconsel=e::l(* incidence function in the residual graph
and in the reversal of the residual of the reversed graph *)letincidence_residualgraphcontextvertex=beginfold_succ_econsgraphvertex[]|>List.filter(is_forwardcontext)|>List.mapforwardend@beginfold_pred_econsgraphvertex[]|>List.filter(is_backwardcontext)|>List.mapbackwardend(* incidence function in the reversal of the residual graph
and in the residual of the reversed graph *)letincidence_reversalgraphcontextvertex=beginfold_succ_econsgraphvertex[]|>List.filter(is_backwardcontext)|>List.mapforwardend@beginfold_pred_econsgraphvertex[]|>List.filter(is_forwardcontext)|>List.mapbackwardend(* Breadth-first search algorithm, with application of
* a function on each arc of the BFS tree. *)letgeneric_bfs:int->(vertex->residual_arclist)->(residual_arc->unit)->vertex->unit=funnb_verticesincidenceiter_funsource->letreached=VMap.createnb_verticesinletfrontier=refQ.emptyinletadd_arcarc=letdest=destinationarcinifVMap.findreacheddest=Nonethen(VMap.addreacheddest();iter_funarc;frontier:=Q.add!frontierdest)inletexplorevertex=List.iteradd_arc(incidencevertex)inVMap.addreachedsource();exploresource;whilenot(Q.is_empty!frontier)doexplore(Q.head!frontier);frontier:=Q.tail!frontierdone(* labels the vertices by their distance to the sink.
This is used to initial the potential of vertices,
and drastically improve the performance of the algorithm. *)letinitialize_potentialcontextsink=letupdatearc=get_potentialcontext(originarc)+1|>set_potentialcontext(destinationarc)inset_potentialcontextsink0;generic_bfscontext.nb_vertices(context.reverse_incidentcontext)updatesink(* checks whether a preflow is maximum.
Happens if no excessive vertex is reverse-reachable from the sink.
*)exceptionBreakletis_maximum_preflowcontext=letcheck_arcarc=ifF.compare(get_excesscontext(destinationarc))F.zero<>0thenraiseBreakintrygeneric_bfscontext.nb_vertices(context.reverse_incidentcontext)check_arccontext.sink;truewithBreak->false(* Push-relabel operations *)(* push excessive flow along an residual arc *)letpushcontextarc=let(u,v)=(originarc,destinationarc)inletexc_u=get_excesscontextuinifis_positiveexc_uthenbeginletdelta=min_flowexc_u(residual_capacitycontextarc)inexc_u--delta|>set_excesscontextu;get_excesscontextv+-delta|>set_excesscontextv;augmentcontextarcdelta;mark_excessivecontextvend(* Augment potential of a vertex to get a lower-potential successor *)letrelabelcontextvertex=context.incidentcontextvertex|>List.map(funarc->get_potentialcontext(destinationarc))|>List.fold_leftmin(get_potentialcontextvertex)|>funpi->set_potentialcontextvertex(pi+1)(* push can be done only on arc with difference of potential = -1 *)letis_admissiblecontextarc=let(u,v)=(originarc,destinationarc)inget_potentialcontextv-get_potentialcontextu=-1(* push as much flow from a vertex as allowed. *)letdischargecontextvertex=context.incidentcontextvertex|>List.filter(is_admissiblecontext)|>List.iter(pushcontext)|>fun()->ifis_positive(get_excesscontextvertex)thenbeginrelabelcontextvertex;mark_excessivecontextvertexend(* Optimization: push vertices ordered by their potential.
(better strategies may be possible). *)letcompare_potentialcontextuv=get_potentialcontextv-get_potentialcontextuletsort_by_potentialcontext=List.sort(compare_potentialcontext)letis_dischargeablecontextv=v<>context.source&&v<>context.sink&&is_positive(get_excesscontextv)letaugmenting_stepcontextcurrently_in_excess=context.excessives<-VSet.create();currently_in_excess|>List.filter(is_dischargeablecontext)|>sort_by_potentialcontext|>List.iter(dischargecontext)letparam_freq_check_preflow=ref1000letcompute_max_preflowcontext=letnb_steps=ref0inletin_excess=ref(extract_excessivescontext)inletcheck_freq=context.nb_vertices/!param_freq_check_preflow+1inletis_maximum()=(!in_excess=[])||(!nb_stepsmodcheck_freq=0&&is_maximum_preflowcontext)inwhilenot(is_maximum())doaugmenting_stepcontext!in_excess;in_excess:=extract_excessivescontext;incrnb_stepsdone(* Maximally push each arc leaving the source,
set the potential of any vertex at distance to sink (optimization). *)letinit_contextcontext=letout_source=context.incidentcontextcontext.sourceininitialize_potentialcontextcontext.sink;set_potentialcontextcontext.sourcecontext.nb_vertices;out_source|>List.map(get_capacitycontext)|>List.fold_leftF.addF.zero|>set_excesscontextcontext.source;out_source|>List.iter(pushcontext);contextletnew_contextgraph~source~sink~reversed~max_capacity~flow=letnb_vertices=G.nb_vertexgraphininit_context{nb_vertices;source;sink;reversed;max_capacity;flow;incident=ifreversedthenincidence_reversalgraphelseincidence_residualgraph;reverse_incident=ifreversedthenincidence_residualgraphelseincidence_reversalgraph;excess=VMap.createnb_vertices;potential=VMap.createnb_vertices;excessives=VSet.create();}letmaxflowgraphsourcesink=letinit_flow()=letflow=EMap.create(G.nb_edgesgraph)inG.fold_edges_e(fune()->EMap.addfloweF.zero)graph();flowinletforward_context=new_contextgraph~source~sink~reversed:false~max_capacity~flow:(init_flow())incompute_max_preflowforward_context;letbackward_context=new_contextgraph~source:sink~sink:source~reversed:true~max_capacity:(get_flowforward_context)~flow:(init_flow())incompute_max_preflowbackward_context;letmax_flow_value=fold_succ_econsgraphsource[]|>List.map(get_flowbackward_context)|>List.fold_leftF.addF.zeroinletfe=matchEMap.findbackward_context.flowewith|Somex->x|None->F.zeroinf,max_flow_valueend(*****************************************************************************)moduletypeG_FORD_FULKERSON=sigtypetmoduleV:Sig.HASHABLEmoduleE:sigtypettypelabelvalsrc:t->V.tvaldst:t->V.tvallabel:t->labelendvaliter_succ_e:(E.t->unit)->t->V.t->unitvaliter_pred_e:(E.t->unit)->t->V.t->unitendmoduletypeFLOWMIN=sigincludeFLOWvalmin_capacity:label->tendmoduleFord_Fulkerson(G:G_FORD_FULKERSON)(F:FLOWMINwithtypelabel=G.E.label)=struct(* redefinition of F *)moduleF=structincludeFtypeu=|FlowofF.t|Infinityletminxy=matchx,ywith|Flow_,Infinity->x|Flowfx,FlowfywhenF.comparefxfy<0->x|(Infinity,_)|(Flow_,Flow_)->yendmoduleMark=structmoduleH=Hashtbl.Make(G.V)typemark=Plus|Minusletmarked=H.create97letunvisited=Queue.create()letclear()=H.clearmarkedletmem=H.memmarkedletsetsetag=assert(not(mems));H.addmarkeds(e,tag);Queue.addsunvisitedletgets:G.E.t*mark=lete,tag=H.findmarkedsin(matchewithNone->assertfalse|Somee->e),tagletnext()=Queue.popunvisitedendmoduleResult=structmoduleH=Hashtbl.Make(structopenGtypet=E.tmoduleU=Util.HTProduct(V)(V)letequale1e2=U.equal(E.srce1,E.dste1)(E.srce2,E.dste2)lethashe=U.hash(E.srce,E.dste)end)letcreate()=H.create97letfind=H.findletflowre=tryfindrewithNot_found->letf=F.flow(G.E.labele)inH.addref;fletchangeopref=tryH.replacere(op(findre)f);withNot_found->assertfalseletgrow=changeF.addletreduce=changeF.subendletis_fullre=F.compare(F.max_capacity(G.E.labele))(Result.flowre)=0letis_emptyre=F.compare(F.min_capacity(G.E.labele))(Result.flowre)=0letset_flowrsta=letrecloopt=ifnot(G.V.equalst)thenlete,tag=Mark.gettinmatchtagwith|Mark.Plus->Result.growrea;loop(G.E.srce)|Mark.Minus->Result.reducerea;loop(G.E.dste)inlooptletgrow_flowrsta=letrecloopub=ifG.V.equalsuthenbeginmatchbwith|F.Infinity->(* source = destination *)assert(G.V.equalst);a|F.Flowf->set_flowrstf;F.addafendelselete,tag=Mark.getuinletl=G.E.labeleinmatchtagwith|Mark.Plus->loop(G.E.srce)(F.minb(F.Flow(F.sub(F.max_capacityl)(Result.flowre))))|Mark.Minus->loop(G.E.dste)(F.minb(F.Flow(F.sub(Result.flowre)(F.min_capacityl))))inlooptF.Infinityletmaxflowgst=letr=Result.create()inletsuccs=G.iter_succ_e(fune->assert(G.V.equals(G.E.srce));lett=G.E.dsteinifnot(Mark.memt||is_fullre)thenMark.sett(Somee)Mark.Plus)gsinletpreds=G.iter_pred_e(fune->assert(G.V.equals(G.E.dste));lett=G.E.srceinifnot(Mark.memt||is_emptyre)thenMark.sett(Somee)Mark.Minus)gsinletinternal_loopa=trywhiletruedolets=Mark.next()insuccs;predsdone;assertfalsewithQueue.Empty->ifMark.memtthengrow_flowrstaelseainletrecexternal_loopa=Mark.clear();Mark.setsNoneMark.Plus;leta'=internal_loopainifF.compareaa'=0thenaelseexternal_loopa'inleta=external_loopF.zeroin(fune->tryResult.findrewithNot_found->F.flow(G.E.labele)),aend