1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842(******************************************************************************
* *
* Fungi Graph *
* Functional Graph algorithms *
* harryk@harryk.dev *
* *
******************************************************************************)openTreeset;;openHeap;;openUnionfind;;openAxiom;;(**
{1:graph Fungi Graph Library}
The graph is held as an adjacency list representation with an optional
Hashtbl to hold edge weights. Self edges are supported by their presence in
both incoming and outgoing sets
{v
Map
elt => inc out adj(Hashtbl)
{
"A" => ("B") ("C") <["C" -> 10.]>
"B" => ("C","B") ("A","B") <["A" -> 20., "B" -> 30.]>
"C" => ("A") ("B") <["B" -> 40.]>
}
v}
{2:build building the graph}
we can construct the graph above by defining our GraphElt with a string
element and float edge like so.
{@ocaml[
module SGraph = Graph.MakeGraph(struct
type t = string
type edge = float
let compare= String.compare
end);;
]}
then we can add our elements into the graph.
{@ocaml[
let sg = SGraph.empty
|> SGraph.add "A"
|> SGraph.add "B"
|> SGraph.add "C"
;;
]}
then we can connect edges between elements. in our case we want a directed
graph with float weights like so. (For unweighted graphs a separate {i
SGraph.add_edge } to connect edges)
{@ocaml[
let sg = sg
|> SGraph.add_weight 10. "A" "C"
|> SGraph.add_weight 20. "B" "A"
|> SGraph.add_weight 30. "B" "B"
|> SGraph.add_weight 40. "C" "A"
;;
]}
we could also construct an adjacency list representation ahead and add all
at once. This may still need the elements to have already been added in the
graph.
{@ocaml[
[
("A", [("C", 10.);]);
("B", [("A", 20.);("B", 30.)]);
("C", [("A", 40.);]);
] |> SGraph.of_weights adjlist
]}
{3:undirected Undirected graphs }
For undirected graphs, the edges representation will be such that the
element will be mirrored in both incoming of the head and outgoing of the
tail. Here we use {i SGraph.of_weights2 } which creates a bidirectional edge
which is structurally an undirected graph (we use {i SGraph.ensure } to
ensure the elements are already in the graph!).
{@ocaml[
[
("A", [("C", 10.);]);
("B", [("A", 20.);("B", 30.)]);
("C", [("A", 40.);]);
] |> SGraph.of_weights2 adjlist
(List.fold_left
(Fun.flip (SGraph.ensure)) SGraph.empty ["A";"B";"C";])
]}
the graph will structurally look like so:
{v
Map
elt => inc out adj(Hashtbl)
{
"A" => ("B") ("C","B") <["C" -> 10., "B" -> 20.]>
"B" => ("C","B") ("A","B") <["A" -> 20., "B" -> 30., "C" -> 40.]>
"C" => ("A","B") ("B","A") <["B" -> 40., "A" -> 10.]>
}
v}
*)(** Implementation signature for algorithms for working with strongly connected
components *)moduletypeSccImpl=sigtypeelttypeadjtypesccnode={link:int;node:elt;indx:int};;moduleNodeMap:Map.Swithtypekey:=eltmoduleAdjSet:TSetwithtypet:=eltmoduleSccTbl:Hashtbl.Swithtypekey:=sccnodemoduleSccSet:TSetwithtypet:=sccnodemoduleSccMap:Map.Swithtypekey:=inttypesolution={disc:sccnodeSccSet.set;onst:eltAdjSet.set;stck:sccnodelist;sccs:eltSccTbl.t;time:int;}typesccedge=(intlist*adjNodeMap.t)valinduced:adjNodeMap.t->eltSccTbl.t->sccedgeSccMap.tvaltarjan:adjNodeMap.t->solutionvalkosaraju:adjNodeMap.t->solutionend(** Implementation signature for generating Cliques *)moduletypeClusterImpl=sigtypeelttypeadjmoduleNodeMap:Map.Swithtypekey:=eltmoduleAdjSet:TSetwithtypet:=eltvalbronkerbosch:adjNodeMap.t->eltAdjSet.setlistvalbronkerbosch2:adjNodeMap.t->eltAdjSet.setlistend(** Signature abstracting edge values from implementation. Measurable could be int
or float or any ast implementing space. concepts such as infinity are
handled separately from the final implementation so they can be used in
places like Path finding or Flow algorithms `*)moduletypeMeasurable=sigincludeSpacetypeedgevalmeasure:edge->twrapend(** Simple adapter for some types to save some boilerplate in some cases
example: Building a Compute module to compute the shortest path using dijkstra
{@ocaml [
(* create a graph type using string nodes and float edges *)
module SGraph = MakeGraph(struct
type t = string
type edge = float
let compare = String.compare
end)
(* ... add nodes and edges ... *)
(* Use compute to build a Path module - use biject to simplify the code *)
module SPath = SGraph.Path.Compute(Biject(Float));;
(* perform the computation *)
let _ = SPath.dijkstra (* arguments... *)
]}*)moduleBiject(T:Space):Measurablewithtypeedge=T.tandtypet=T.t=structincludeTtypet=T.ttypeedge=tletcompare=T.compareletmeasuree=`Valeend(** Algorithms for path finding *)moduletypePathImpl=sigtypeelttypeadjtypeedgetypeweightstype'bctxmoduleNodeMap:Map.Swithtypekey:=eltmoduleAdjSet:TSetwithtypet:=eltmoduleEdgeSet:TSetwithtypet:=(elt*elt)type'cpathelt={from:elt;next:elt;via:elt;value:'c;}typepath:=(edgeoptionpathelt)listvalmkpath:elt->elt->'a->'apatheltmoduleCompute(Measure:Measurablewithtypet=edgeandtypeedge=edge):sigtypemeasure=Measure.twrapmodulePathList:Ordinalwithtypeorder=measureandtypet=measurepatheltmodulePathHeap:FibHeapwithtypenode=measurepatheltandtypeorder=measuremodulePathSet:TSetwithtypet:=measurepatheltvaldijkresolve:elt->(elt*measure)list->(elt*elt*measure)list->(elt*measure)listvaldijkstra:elt->elt->adjNodeMap.t->((elt*measure)list)valastar:(elt->measure)->elt->elt->adjNodeMap.t->(elt*measure)listvalbellresolve:elt->elt->PathList.tPathSet.set->(elt*measure)listvalbellmanford:elt->elt->adjNodeMap.t->((elt*measure)list*(elt*elt)EdgeSet.set)valjohnsons:elt->adjNodeMap.t->(adjNodeMap.t*(elt->elt->edge->edge))valfloydwresolve:elt->elt->measurearrayarray->intwraparrayarray->(int*elt)list->(elt*measure)listoptionvalfloydwarshall:?negcycles:(bool)->adjNodeMap.t->(measurearrayarray*intwraparrayarray*(int*elt)list)endvalhierholzer:?endpoints:(adjNodeMap.t->(elt*elt*int)option)->adjNodeMap.t->(eltlist)optionvalnaivedfs:adjNodeMap.t->(pathctx->bool)->elt->pathvalnaivebfs:adjNodeMap.t->(pathctx->bool)->elt->pathend(** Vertex carries node information *)moduletypeVertexImpl=sigtypeelttypeadjtypeedgetypeweightsincludeSet.OrderedTypewithtypet:=adjmoduleNodeMap:Map.Swithtypekey:=eltvalempty:elt->adjvalweights:elt->adjNodeMap.t->weightsvaledge:elt->elt->adjNodeMap.t->edgevaledge2:elt->weights->edgevaledgeo:elt->elt->adjNodeMap.t->edgeoptionvalupdate:elt->weights->edge->unitvalensure:elt->weights->edge->unitend(** Routines for dumping out graphs *)moduletypeSerDe=sigtypeelttypeedgevalstring_of_elt:elt->stringvalstring_of_wgt:edge->stringvalelt_of_string:string->eltvalwgt_of_string:string->edgeend(** the graph node (GraphElt.t) and edge types *)moduletypeGraphElt=sigtypeedgeincludeSet.OrderedTypeend(** Graph Signature *)moduletypeGraph=sigtype'attypeelt(* Main graph node *)typeedge(* Type of edge *)(** Adjacency set hold either incoming or outgoing elements *)moduleAdjSet:TSetwithtypet:=elt(** Weights hold a mapping of a Vertex node to outgoing edges in the
adjacency list with the weight values of type edge *)moduleWeights:Hashtbl.Swithtypekey:=elt(** Vertex adjacency type *)typeadj={inc:eltAdjSet.set;(* incoming set *)out:eltAdjSet.set;(* outgoing set *)lab:elt;(* label *)edg:edgeWeights.t;(* Weights edges *)}(** traversal context for breadth and depth first search *)type'bctx={stop:bool;(* whether to stop a recurse *)prev:(elt*adj)option;(* the previous element, None if start *)elt:elt;(* the current node *)vis:eltAdjSet.set;(* the visited nodes *)acc:'b;(* the accumulator *)vtx:adj;(* the node vertex information *)}(** Mapping of nodes to their adjacency sets and Vertex information *)moduleNodeMap:Map.Swithtypekey:=elt(** Set for holding edges for use in some algorithms *)moduleEdgeSet:TSetwithtypet:=(elt*elt)(** Vertex implementation holding adjacency information *)moduleVertex:VertexImplwithtypeelt:=elt(* The node element *)andtypeadj:=adj(* The adjacency information *)andtypeweights:=edgeWeights.t(* Hashtbl holding edge weights *)andtypeedge:=edge(* The edge type *)andmoduleNodeMap:=NodeMap(* Module for manipulating the map *)moduleScc:SccImplwithtypeelt:=elt(* The node element *)andtypeadj:=adj(* The adjacency information *)andmoduleNodeMap:=NodeMap(* Graph map manipulation *)andmoduleAdjSet:=AdjSet(* Adjacency set manipulation *)moduleCluster:ClusterImplwithtypeelt:=elt(* The node element *)andtypeadj:=adj(* The adjacency information *)andmoduleNodeMap:=NodeMap(* Graph map manipulation *)andmoduleAdjSet:=AdjSet(* Adjacency set manipulation *)modulePath:PathImplwithtypeelt:=elt(* The node element *)andtypeadj:=adj(* The adjacency information *)andtypeedge:=edge(* The edge type *)andtypeweights:=edgeWeights.t(* Hashtbl for holding weights *)andtype'bctx:='bctx(* traversal context *)andmoduleNodeMap:=NodeMap(* Graph map manipulation *)andmoduleAdjSet:=AdjSet(* Adjacency set manipulation *)andmoduleEdgeSet:=EdgeSet(* Edge Set manipulation *)(** converting the graph into serializable formats *)moduleSerialize(_:SerDewithtypeelt:=eltandtypeedge:=edge):sigmoduleStyleTbl:Hashtbl.Swithtypekey=stringmoduleAttrbTbl:Hashtbl.Swithtypekey=stringmoduleClstrTbl:Hashtbl.Swithtypekey=inttypeattrs:=stringStyleTbl.ttypeattrmap:=(stringStyleTbl.t)AttrbTbl.ttypeclstmap:=(stringStyleTbl.t)ClstrTbl.tvalto_csv:adjNodeMap.t->((unit->string)Seq.t)Seq.tvalto_dot:?dir:bool->?sub:bool->string->attrs->attrmap->attrmap->adjNodeMap.t->(unit->string)Seq.tSeq.tvalto_dot_cluster:?dir:bool->string->(int->intlist->string)->attrs->clstmap->attrmap->attrmap->(intlist*adjNodeMap.t)Scc.SccMap.t->(unit->string)Seq.tSeq.tend(** Graph Flow algorithms *)moduleFlow(Measure:Measurablewithtypet=edgeandtypeedge=edge):sigtypemeasure=Measure.twraptypestate={mutableflow:edge}moduleFlowtbl:Hashtbl.Swithtypekey=(elt*elt)valfordfulkerson:?maxit:int->stateFlowtbl.t->elt->elt->adjNodeMap.t->measurevaledmondskarp:?maxit:int->stateFlowtbl.t->elt->elt->adjNodeMap.t->measureend(** Matching algorithms on graphs e.g gale-shapely *)moduleMatching:sigvalhall:adjNodeMap.t->eltAdjSet.set->boolmoduleCompute(_:Measurablewithtypet=edgeandtypeedge=edge):sigvalgaleshapely:adjNodeMap.t->eltAdjSet.set->eltAdjSet.set->(elt*elt)EdgeSet.setendend(** Minimum Spanning Trees algorithms on graphs i.e kruskal and prims *)moduleSpan:functor(Measure:Measurablewithtypeedge=edge)->sigmoduleEdgeDisj:UnionFindwithtypeelt=eltvalcost:adjNodeMap.t->Measure.tvalprim:elt->adjNodeMap.t->adjNodeMap.tvalkruskal:?connect:(Measure.edge->elt->elt->adjNodeMap.t->adjNodeMap.t)->adjNodeMap.t->adjNodeMap.tend(** An empty graph *)valempty:adjNodeMap.tvalequal:elt->elt->bool(** Add a new node with its label -> ( ... , nodedata), this will replace
existing entries *)valadd:elt->adjNodeMap.t->adjNodeMap.t(** only adds and updates if the value was not already present, otherwise
leaves as is to avoid overwriting *)valensure:elt->adjNodeMap.t->adjNodeMap.t(** same as ensure but verifies a sequence of elements *)valensureall:adjNodeMap.t->eltSeq.t->adjNodeMap.t(** only adds and updates if the value was not already present, otherwise
leaves as is but adds edge from nodeKey as a contingency *)valensureof:elt->elt->adjNodeMap.t->adjNodeMap.t(** directly place elements into the graph - does not check if all edges
are resolvable *)valemplace:adj->elt->adjNodeMap.t->adjNodeMap.t(** directly place elements into the graph - ensures edges exist and
adds them otherwise *)valemplaceadj:adj->elt->adjNodeMap.t->adjNodeMap.t(**
Add a directed edge [(tail)] --> [(head)] such that the tails outgoing
set points to the heads incoming set. nonexistent nodes don't resolve
and no error is raised in that case - it is up to the user to ensure the nodes already exist in
the graph e.g using `Graph.ensure` function
*)valadd_edge:elt->elt->adjNodeMap.t->adjNodeMap.t(** Bidirectionally add an edge - if they nodes exist *)valadd_edge2:elt->elt->adjNodeMap.t->adjNodeMap.t(** add all elements *)valadd_all:elt->eltlist->adjNodeMap.t->adjNodeMap.t(** add all elements (bidirectional) *)valadd_all2:elt->eltlist->adjNodeMap.t->adjNodeMap.t(** add elements with respective weights *)valallweighted:elt->(elt*edge)list->adjNodeMap.t->adjNodeMap.t(** bidirectionally add all elements with respective weights - if they exist *)valallweighted2:elt->(elt*edge)list->adjNodeMap.t->adjNodeMap.t(** add edge with a weight if the nodes exist *)valadd_weight:edge->elt->elt->adjNodeMap.t->adjNodeMap.t(** bidirectional add edge with a weight if the nodes exist *)valadd_weight2:edge->elt->elt->adjNodeMap.t->adjNodeMap.t(** build graph from list - nodes should have already been added *)valof_list:(elt*eltlist)list->adjNodeMap.t->adjNodeMap.t(** build a bidirectional (undirected) graph *)valof_list2:(elt*eltlist)list->adjNodeMap.t->adjNodeMap.t(** build a directed graph with weight values -> edges already existing *)valof_weights:(elt*(elt*edge)list)list->adjNodeMap.t->adjNodeMap.t(** build an undirected (bidirecitional) graph with weights *)valof_weights2:(elt*(elt*edge)list)list->adjNodeMap.t->adjNodeMap.t(** number of nodes in the graph *)valcardinal:adjNodeMap.t->int(** Connect all nodes in the graph to form a complete graph *)valcomplete:adjNodeMap.t->adjNodeMap.t(** Get the vertex information of a node in the graph *)valvertexof:elt->adjNodeMap.t->adj(** Set of nodes forming the incoming set *)valincomingof:elt->adjNodeMap.t->eltAdjSet.set(** Incoming nodes and respective weights on the edges *)valincweights:elt->adjNodeMap.t->eltAdjSet.set*edgeWeights.t(** Set of nodes in the outgoing edges *)valoutgoingof:elt->adjNodeMap.t->eltAdjSet.set(** Outgoing nodes and respective weights on the edges *)valoutweights:elt->adjNodeMap.t->eltAdjSet.set*edgeWeights.t(** both incoming and outgoing edges of a graph - self edges included *)valneighbours:elt->adjNodeMap.t->eltAdjSet.set(** both incoming and outgoing edges of a graph - self edges NOT included *)valxorneighbors:elt->adjNodeMap.t->eltAdjSet.set(** items in both incoming and outgoing edges of a graph - self edges included *)valmutuals:elt->adjNodeMap.t->eltAdjSet.set(** items in both incoming and outgoing edges of a graph - self edges NOT included *)valxormutuals:elt->adjNodeMap.t->eltAdjSet.set(** degree of a single node *)valdegree:elt->adjNodeMap.t->int(** incoming + outgoing degree *)valiodeg:elt->adjNodeMap.t->(int*int)(** eulerian circuit test for undirected graphs (all edges visited exactly
once) *)valundcircuit:adjNodeMap.t->bool(** eulerian circuit test for directed graphs (all edges visited exactly once) *)valdircircuit:adjNodeMap.t->bool(** undirected eulerian path for undirected graph return start and end points *)valundeulpath:adjNodeMap.t->(elt*elt*int)option(** directed eulerian path for directed graph return start and end points any circuit is an eulerian path *)valdireulpath:adjNodeMap.t->(elt*elt*int)option(** degree of incoming nodes *)valincdeg:elt->adjNodeMap.t->int(** degree of outgoing nodes *)valoutdeg:elt->adjNodeMap.t->int(** all edges as pairs in the form (from, to) for edge from -> to *)valallpairs:adjNodeMap.t->(elt*elt)list(** all edges and respective weights *)valallweights:adjNodeMap.t->(elt*elt*edge)list(** Removes a node from the graph - weights aren't altered and may still be
available from an opposite end of the edge depending oon how the graph is
structured *)valremove:elt->adjNodeMap.t->adjNodeMap.t(** Removes all nodes in the list from the graph - weights aren't altered and may still be
available from an opposite end of the edge depending oon how the graph is
structured *)valremoveall:adjNodeMap.t->eltlist->adjNodeMap.t(** edge removal, call twice flipped if undirected graph *)valremove_edge:adjNodeMap.t->elt->elt->adjNodeMap.t(** Remove self edges from a graph *)valcull:adjNodeMap.t->adjNodeMap.t(** Remove "free" vertices from a graph (no inc or out) *)valprune:adjNodeMap.t->adjNodeMap.t(** find "dangling" nodes in the graph with no incoming or outgoing edges *)valallfree:adjNodeMap.t->eltAdjSet.set(** toposort (happens-before) - assumes the graph is acyclic (DAG) otherwise the
result is wrong. *)valtoposort:adjNodeMap.t->eltlist(* traversal state *)typestate:=((elt*adj)option*elt)(* traversal callback *)type('a,'b)cback:='a->'bctx->'bctx(** breadth first search starting from start node applying f until stop is
true or queue is empty applying f on each node and b on backtrack
filters already visited nodes, in bidirectional cases,
backedges will not be visited twice
*)valbfs:(stateQueue.t,'b)cback->(stateQueue.t,'b)cback->adjNodeMap.t->elt->'b->'b(** depth first search starting from start node applying f until returns
true applying f until returns
true or stack is empty applying f on each node and b on backtrack *)valdfs:(stateStack.t,'b)cback->(stateStack.t,'b)cback->adjNodeMap.t->elt->'b->'b(** Get adjacency list of a node *)valadj_list_of:elt->adjNodeMap.t->eltlist(** Get adjacency list of a node *)valadj_seq_of:elt->adjNodeMap.t->eltSeq.t(** swap the incoming and outgoing edge direction - preserving edge weights
use transpose2 if you do not use edge weights! *)valtranspose:adjNodeMap.t->adjNodeMap.t(** swap the incoming and outgoing edge direction - WITHOUT preserving edge weights *)valtranspose2:adjNodeMap.t->adjNodeMap.t(** collect graph into a simple [(key, adj list), ...] *)valoutlist:adjNodeMap.t->(elt*eltAdjSet.set)list(** create a graph from 2d adjacency matrix with keys defining which elt maps to what index *)valof_matrix:intarrayarray->(int*elt)list->adjNodeMap.t(** convert a graph to a 2d array and a key list defining the elt for each array index *)valadjmatrix:adjNodeMap.t->intarrayarray*(int*elt)list(** convert a graph to 2d weights (edge values) and a key list defining the elt for each array index *)valwgtmatrix:(edge->'b)->'b->adjNodeMap.t->('barrayarray)*((int*elt)list)(** inout degree matrix along the diagonal - can be used to construct a laplacian *)valdegmatrix:adjNodeMap.t->intarrayarray*(int*elt)list(** graph -> incidence matrix, node array, elt array
useful in spectral clustering: https://math.stackexchange.com/a/3726603 *)valincmatrix:adjNodeMap.t->intarrayarray*eltarray*(elt*elt)array(** in and out degree for each elt *)valdegtable:(elt,int*int)Hashtbl.t->adjNodeMap.t->unit(** create a set of all edges *)valedgeset:adjNodeMap.t->(elt*elt)EdgeSet.set(** create a sequence of edges *)valedgeseq:adjNodeMap.t->(elt*elt)Seq.t(** create a sequence of edges and weight values *)valedgewgtseq:adjNodeMap.t->(elt*elt*edge)Seq.t(** like edgeset but returns the size to avoid recomputing if needed. returns
the node count, edge count and edge set in that order *)valedgesetsz:adjNodeMap.t->int*int*(elt*elt)EdgeSet.set(** whether edge from f to t exists in nodeMap *)valhas_edge:elt->elt->adjNodeMap.t->bool(** acyclic if i cant form a cycle from each element *)valis_acyclic:adjNodeMap.t->boolend(** simple adapter for some types to save some boilerplate in some cases - in this case making edges unitary *)modulePlain(T:Set.OrderedType):GraphEltwithtypeedge=unit=structtypet=T.tletcompare=T.comparetypeedge=unitend(** Functor to create a graph using an adjacency set representation.
example: To create a graph of String nodes and float edge weights
{@ocaml[
module SGraph = Graph.MakeGraph(struct
type t = string
type edge = float
let compare= String.compare
end);;
...
]}
The structure of the graph is roughly as follows:
For a graph consisting of nodes A,B,C with a self edge on A
{[
{
"A" => { inc:("A","B"); out:("C","A"); <{"C"-> 1.0,"A" -> 0.}>};;
"B" => { inc:("C"); out:("A"); <{"A"-> 1.0}>};;
"C" => { inc:("A"); out:("B"); <{"B"-> 2.0}>};;
...
}
]}
This is a simple map from each element to the `adj` structure holding the
incoming and outgoing edges in separate sets and a Hashtbl of edge values
*)moduleMakeGraph(Unique:GraphElt):Graphwithtypeelt:=Unique.tandtypeedge:=Unique.edge=structlet(let*)=Option.bindtypeelt=Unique.ttypeedge=Unique.edge(** compare 2 nodes in the graph *)letequallnodernode=(Unique.comparelnodernode)=0(** Weights hold edge values should they exist. elt is the `to` direction of
the weight, on undirected graphs it may be duplicated on both nodes
vertex values as a to in one and a from in another *)moduleWeightNode=structtypet=eltletequal=equallethash=Hashtbl.hashendmoduleWeights=Hashtbl.Make(WeightNode)(** Module for manipulating the Set structure holding the Adjacency list *)moduleAdjSet=TreeSet(Unique)(** The adjacency representation *)typeadj={inc:eltAdjSet.set;(* incoming set *)out:eltAdjSet.set;(* outgoing set *)lab:elt;(* label *)edg:edgeWeights.t;(* Weights edges *)}(** Module for manipulating the Map (Node -> (set , set , label)) *)moduleNodeMap=Map.Make(Unique)(** Module for manipulating pairs of elements used mainly to represent edges *)moduleEdgeSet=TreeSet(structtypet=(elt*elt)letcompare(x,y)(x',y')=matchUnique.comparexx'with|0->Unique.compareyy'|z->zend)(** Nodes forming the graph *)moduleVertex=structtypet=adjletcompare=fun{lab=lnode;_}{lab=rnode;_}->Unique.comparelnodernodeletemptylbl={inc=AdjSet.empty;out=AdjSet.empty;lab=lbl;edg=(Weights.create1)}letweightsng=let{edg;_}=NodeMap.findnginedgletedgeftg=Weights.find(weightsfg)tletedge2to=Weights.findotletedgeoftg=Weights.find_opt(weightsfg)tletupdatetov=Weights.replaceotvletensuretov=ifWeights.memotthen()elseWeights.addotvend(** Adjacency list graph definition **)type+'at=(Vertex.t)NodeMap.t(** An empty graph *)letempty=NodeMap.empty(** Add a new node with its label -> ( ... , nodedata), this will replace
existing entries *)letaddnodekeynodeMap=NodeMap.addnodekey(Vertex.emptynodekey)nodeMap;;(** only adds and updates if the value was not already present, otherwise
leaves as is to avoid overwriting *)letensurenodeKeynodeMap=NodeMap.updatenodeKey(funv->matchvwith|None->Some(Vertex.emptynodeKey)|v'->v')nodeMap;;(** same as ensure but verifies a sequence of elements *)letensureallnodeseqnodeMap=Seq.fold_left(Fun.flipensure)nodeseqnodeMap;;(** only adds and updates if the value was not already present, otherwise
leaves as is but adds edge from nodeKey as a contingency *)letensureofnodeFromnodeKeynodeMap=NodeMap.updatenodeKey(funv->matchvwith|None->letv'=Vertex.emptynodeKeyinSome{v'withinc=(AdjSet.addnodeFromv'.inc)}|Somev'->Some{v'withinc=(AdjSet.addnodeFromv'.inc)})(addnodeFromnodeMap);;(** directly place elements into the graph - does not check if all edges
are resolvable *)letemplace{inc;out;edg;_}nodekeynodeMap=NodeMap.addnodekey{inc;out;edg;lab=nodekey}nodeMap;;(** directly place elements into the graph - ensures edges exist and
adds them otherwise *)letemplaceadj{inc;out;edg;_}nodekeynodeMap=nodeMap|>(AdjSet.fold(funelac->ensureofnodekeyelac)inc)|>(AdjSet.fold(funelac->ensureofelnodekeyac)out)|>NodeMap.addnodekey{inc;out;edg;lab=nodekey};;(**
Add a directed edge [(tail)] --> [(head)] such that the tails outgoing
set points to the heads incoming set. nonexistent nodes don't resolve
and no error is raised in that case - it is up to the user to ensure the nodes already exist in
the graph e.g using `Graph.ensure` function
Find the tail of the directed edge
Update with outgoing
Find the head of the directed edge
Update with incoming
*)letadd_edgenodeFromnodeTonodeMap=(*(NodeMap.update nodeFrom (fun x -> let* (fromIncoming, fromOutgoing, label, wgts) = x in*)(NodeMap.updatenodeFrom(funx->let*{out=frOutgoing;_}asa=xinSome{awithout=(AdjSet.addnodeTofrOutgoing)})nodeMap)|>NodeMap.updatenodeTo(funx->let*{inc=toIncoming;_}asb=xinSome{bwithinc=(AdjSet.addnodeFromtoIncoming)});;(** Bidirectionally Find the tail of the directed edge
Update with outgoing and incoming
Find the head of the directed edge
Update with incoming and outgoing*)letadd_edge2nodeFromnodeTonodeMap=(*(NodeMap.update nodeFrom (fun x -> let* (fromIncoming, fromOutgoing, label, wgts) = x in*)(NodeMap.updatenodeFrom(funx->let*{out=frOutgoing;inc=frIncoming;_}asa=xinSome{awithout=(AdjSet.addnodeTofrOutgoing);inc=(AdjSet.addnodeTofrIncoming)})nodeMap)|>NodeMap.updatenodeTo(funx->let*{inc=toIncoming;out=toOutgoing;_}asb=xinSome{bwithinc=(AdjSet.addnodeFromtoIncoming);out=(AdjSet.addnodeFromtoOutgoing)});;(** Find the tail of the directed edge
Update with outgoing
Find the head of the directed edge
Update with incoming
Add nodeFrom nodeTo with weight values on from end *)letadd_weightweightValuenodeFromnodeTonodeMap=(NodeMap.updatenodeFrom(funx->let*{out=frOutgoing;edg=wgts;_}asa=xinlet_=Weights.addwgtsnodeToweightValueinSome{awithout=(AdjSet.addnodeTofrOutgoing)})nodeMap)|>NodeMap.updatenodeTo(funx->let*{inc=toIncoming;_}asb=xinSome{bwithinc=(AdjSet.addnodeFromtoIncoming)});;(** Find the tail of the directed edge
Update with outgoing
Find the head of the directed edge
Update with incoming
Add bidirectional nodeFrom nodeTo with weight values on both ends *)letadd_weight2weightValuenodeFromnodeTonodeMap=(NodeMap.updatenodeFrom(funx->let*{inc=frIncoming;out=frOutgoing;edg=wgts;_}asa=xinlet_=Weights.addwgtsnodeToweightValueinSome{awithinc=(AdjSet.addnodeTofrIncoming);out=(AdjSet.addnodeTofrOutgoing)})nodeMap)|>NodeMap.updatenodeTo(funx->let*{inc=toIncoming;out=toOutgoing;edg=wgts;_}asb=xinlet_=Weights.addwgtsnodeFromweightValueinSome{bwithinc=(AdjSet.addnodeFromtoIncoming);out=(AdjSet.addnodeFromtoOutgoing);});;(** create edges from nodeFrom to all nodeTo in nodeToList *)letrecadd_allnodeFromnodeToListnodeMap=matchnodeToListwith|[]->nodeMap|nodeTo::rest->add_edgenodeFromnodeTo(add_allnodeFromrestnodeMap);;(** same as add_all except it add bidirectional edges which can be
interpreted as undirected edges too *)letrecadd_all2nodeFromnodeToListnodeMap=matchnodeToListwith|[]->nodeMap|nodeTo::rest->add_edge2nodeFromnodeTo(add_all2nodeFromrestnodeMap);;(** creates a complete graph without erasing existing edges *)letcompletenodeMap=NodeMap.fold(funel{out;_}ac->NodeMap.fold(funel'_ac'->ifAdjSet.memel'outthenac'elseadd_edge2elel'ac')nodeMapac)nodeMapnodeMap;;(** creates edges from nodeFrom to all nodeTo in the list with their edge
values alongside *)letrecallweightednodeFromnodeToListnodeMap=matchnodeToListwith|[]->nodeMap|(nodeTo,nodeVal)::rest->add_weightnodeValnodeFromnodeTo(allweightednodeFromrestnodeMap);;(** same as allweighted but for bidirectional or undirected graphs *)letrecallweighted2nodeFromnodeToListnodeMap=matchnodeToListwith|[]->nodeMap|(nodeTo,nodeVal)::rest->add_weight2nodeValnodeFromnodeTo(allweighted2nodeFromrestnodeMap);;(** number of nodes in the graph *)letcardinal=NodeMap.cardinal;;(** Creates a graph given a node and outgoing edge, incoming edges will be
resolved naturally. All nodes should already be available in the map *)letrecof_listadjListnodeMap=matchadjListwith|[]->nodeMap|(nodeFrom,nodeJoinList)::rest->add_allnodeFromnodeJoinList(of_listrestnodeMap);;(** Creates a graph given a node and outgoing edge (bidir), incoming edges will be
resolved naturally. All nodes should already be available in the map *)letrecof_list2adjListnodeMap=matchadjListwith|[]->nodeMap|(nodeFrom,nodeJoinList)::rest->add_all2nodeFromnodeJoinList(of_list2restnodeMap);;(** Creates a graph given a node and outgoing edge, incoming edges will be
resolved naturally. All nodes should already be available in the map *)letrecof_weightsadjListnodeMap=matchadjListwith|[]->nodeMap|(nodeFrom,nodeJoinList)::rest->allweightednodeFromnodeJoinList(of_weightsrestnodeMap);;(** Creates a graph given a node and outgoing edge, incoming edges will be
resolved naturally. All nodes should already be available in the map *)letrecof_weights2adjListnodeMap=matchadjListwith|[]->nodeMap|(nodeFrom,nodeJoinList)::rest->allweighted2nodeFromnodeJoinList(of_weights2restnodeMap);;(** vertex of an element *)letvertexof=NodeMap.find;;(** [ incomingof identity (Graph.t) AdjSet.t]
Incoming set of nodes *)letincomingofnodegame=let{inc;_}=NodeMap.findnodegameininc(** incoming edges with respective weights *)letincweightsnodegame=let{inc;edg;_}=NodeMap.findnodegamein(inc,edg)(** [ incomingof identity (Graph.t) AdjSet.t]
Outgoing set of nodes *)letoutgoingofnodegame=let{out;_}=NodeMap.findnodegameinout(** outgoing edges with respective weights *)letoutweightsnodegame=let{out;edg;_}=NodeMap.findnodegamein(out,edg)(** both incoming and outgoing edges of a graph - self edges included *)letneighboursnodegame=let{inc;out;_}=NodeMap.findnodegamein(AdjSet.unionincout)(** both incoming and outgoing edges of a graph - self edges NOT included *)letxorneighborsnodegame=let{inc;out;_}=NodeMap.findnodegameinAdjSet.removenode(AdjSet.unionincout);;(** items in both incoming and outgoing edges of a graph - self edges included *)letmutualsnodegame=let{inc;out;_}=NodeMap.findnodegamein(AdjSet.interincout);;(** items in both incoming and outgoing edges of a graph - self edges NOT included *)letxormutualsnodegame=let{inc;out;_}=NodeMap.findnodegameinAdjSet.removenode(AdjSet.interincout);;(** degree of incoming nodes *)letincdegnodenodeMap=AdjSet.cardinal@@incomingofnodenodeMap;;(** degree of outgoing nodes *)letoutdegnodenodeMap=AdjSet.cardinal@@outgoingofnodenodeMap;;(** number of incoming and outgoing edges *)letdegreenodenodeMap=incdegnodenodeMap+outdegnodenodeMap;;(** incoming and outgoing degree *)letiodegnodenodeMap=(incdegnodenodeMap,outdegnodenodeMap);;(** eulerian circuit test for undirected graphs (all edges visited exactly
once) *)letundcircuitnodeMap=NodeMap.for_all(fun_{inc;out;_}->(AdjSet.cardinalout+AdjSet.cardinalinc)mod2==0)nodeMap;;(** eulerian circuit test for directed graphs (all edges visited exactly once) *)letdircircuitnodeMap=NodeMap.for_all(fun_{inc;out;_}->(AdjSet.cardinalout)==(AdjSet.cardinalinc))nodeMap;;(** undirected eulerian path for undirected graph return start and end points *)letundeulpathnodeMap=let(even,odd,sz,pnts)=NodeMap.fold(funk{inc;out;_}(even,odd,sz',(l,r))->letdeg=(AdjSet.cardinalout+AdjSet.cardinalinc)inifdegmod2==0then(even+1,odd,sz'+1,(l,r))elseifodd=0then(even,odd+1,sz'+1,(l,Somek))else(even,odd+1,sz'+1,(Somek,l)))nodeMap(0,0,0,(None,None))in(* either all vertices are even or exactly 2 have odd degree *)ifeven=szthen(* all vertices are even - pick any 2 nodes - or a circuit *)let(k,_)=NodeMap.choosenodeMapinSome(k,k,sz)elseifodd=2thenmatchpntswith|Somel,Somer->Some(l,r,sz)(* ideally unreachable when odd = 2 *)|_->NoneelseNone;;(** directed eulerian path for directed graph return start and end points
any circuit is an eulerian path
*)letdireulpathnodeMap=(* in = 1, out = 1, eq, count *)let(din,dou,deq,sze,pnts)=NodeMap.fold(funk{inc;out;_}(din,dout,deq,cn,(start,fin))->letindeg=AdjSet.cardinalincinletoudeg=AdjSet.cardinaloutinletio=((indeg-oudeg)=1)inletou=((oudeg-indeg)=1)inleteq=((indeg=oudeg))in(din+(Bool.to_intio),dout+(Bool.to_intou),deq+(Bool.to_inteq),cn+1,((* articulate the start and end *)(ifouthenSomekelsestart),(ifiothenSomekelsefin))))nodeMap(0,0,0,0,(None,None))in(* degree check for euler path *)letcheck=(* all vertices have equal in and out degree *)(deq=sze)||(* at most 1 has each of "greater by 1" degree *)(din=1)&&(dou=1)&&(deq=sze-2)inmatch(check,pnts)with|(false,_)->None(* Even case: The circuit is the euler path *)|(true,(None,None))->let(k',_)=NodeMap.choosenodeMapinSome(k',k',sze)(* Odd case: Verify there must be at least 2 points for start and end *)|(_,(Somex,Somey))->Some(x,y,sze)(* Ideally this case shouldn't be reached but it should mean no path *)|_->None;;(** all edges as pairs in the form (from, to) for edge from -> to *)letallpairsnodeMap=NodeMap.fold(funk{out;_}ac->AdjSet.fold(funelac'->(k,el)::ac')outac)nodeMap[];;(** all edges and respective weights *)letallweightsnodeMap=NodeMap.fold(funk{out;edg;_}ac->AdjSet.fold(funelac'->(k,el,Weights.findedgel)::ac')outac)nodeMap[];;(** Removes a node from the graph - weights aren't altered and may still be
available from an opposite end of the edge depending oon how the graph is
structured *)letremovedelnodenodeMap=let{inc=incoming;out=outgoing;_}=(NodeMap.finddelnodenodeMap)inNodeMap.removedelnode(AdjSet.fold(funnodelabelupdatemap->NodeMap.updatenodelabel(funx->let*{inc=deepinc;out=deepout;_}asa=xinSome{awithinc=AdjSet.removedelnodedeepinc;out=AdjSet.removedelnodedeepout;})updatemap)(AdjSet.unionincomingoutgoing)nodeMap);;(** Removes all nodes from the graph - weights aren't altered and may still be
available from an opposite end of the edge depending oon how the graph is
structured *)letremovealldelnodelistnodeMap=List.fold_left(funaccdelnode->removedelnodeacc)delnodelistnodeMap;;(** edge removal, call twice flipped if undirected graph *)letremove_edgenodeMapnodeFromnodeTo=(NodeMap.updatenodeFrom(funx->let*{out=fromOutgoing;_}asa=xinSome{awithout=(AdjSet.removenodeTofromOutgoing)})nodeMap)|>(NodeMap.updatenodeTo(funx->let*{inc=toIncoming;_}asb=xinSome{bwithinc=(AdjSet.removenodeFromtoIncoming)}));;(** Remove self edges from a graph - TODO: could be more efficient - perhaps *)letcullgraph=graph|>NodeMap.to_seq|>Seq.filter(fun(elt,{out;_})->AdjSet.memeltout)|>Seq.fold_left(fung(elt,({inc;out;_}asa))->NodeMap.updateelt(fun_->Some{awithinc=(AdjSet.removeeltinc);out=(AdjSet.removeeltout);})g)graph;;(** find "dangling" nodes in the graph with no incoming or outgoing edges *)letallfreegraph=graph|>NodeMap.to_seq|>Seq.filter_map(fun(elt,{out;inc;_})->ifAdjSet.is_emptyinc&&AdjSet.is_emptyoutthenSomeeltelseNone)|>Seq.fold_left(Fun.flipAdjSet.add)AdjSet.empty;;(** Remove "free" vertices from a graph (no inc or out) *)letprunegraph=graph|>NodeMap.to_seq|>Seq.filter(fun(_,{out;inc;_})->AdjSet.is_emptyinc&&AdjSet.is_emptyout)|>Seq.fold_left(fung(elt,_)->NodeMap.removeeltg)graph;;(** a graph traversal context *)type'bctx={(* whether to stop a recurse or traversal *)stop:bool;(* the previous node vertex data *)prev:(elt*adj)option;(* the current node *)elt:elt;(* the visited nodes so far in the traversal *)vis:eltAdjSet.set;(* the accumulator *)acc:'b;(* the node vertex information *)vtx:adj;}letmkctxplvaso={prev=p;elt=l;vis=v;acc=a;stop=s;vtx=o;};;(** breadth first search starting from start node applying f until stop is
true or queue is empty applying f on each node and b on backtrack
filters already visited nodes, in bidirectional cases,
backedges will not be visited twice
*)letbfsfbgraphstartinit=letque=Queue.create()inlet_=Queue.add(None,start)queinletvisited=AdjSet.emptyinletrecitervisnxtacc=ifQueue.is_emptynxtthen(vis,acc)elselet(prev,label)=Queue.takenxtinletvtx=vertexoflabelgraphinlet{out;_}=vtxinlet{stop;vis=vis';acc=acc';_}=fque(mkctx(prev)labelvisaccfalsevtx)inlet(vis'',acc'')=ifstopthen(vis',acc')elseletdiff=AdjSet.diffoutvis'inlet_=AdjSet.iter(funx->Queue.add((Some(label,vtx)),x)nxt)(diff)initer(AdjSet.uniondiffvis')nxtacc'in(vis'',(bque(mkctxprevlabelvis''acc''stopvtx)).acc)inlet(_,acc)=itervisitedqueinitinacc;;(** depth first search starting from start node applying f until returns
true applying f until returns
true or stack is empty applying f on each node and b on backtrack *)letdfsfbgraphstartinit=letstck=Stack.create()inlet_=Stack.push(None,start)stckinletvisited=AdjSet.emptyinletrecitervisnxtacc=ifStack.is_emptynxtthen(vis,acc)elselet(prev,label)=Stack.popnxtinifAdjSet.memlabelvistheniter(vis)nxtaccelseletvtx=vertexoflabelgraphinlet{out;_}=vtxin(* callback can functionally rewrite these values *)let{stop;acc=acc';vis=vis';_}=fnxt(mkctx(prev)labelvisaccfalsevtx)inlet(vis'',acc'')=(ifstopthen(vis',acc')elselet_=AdjSet.iter(funx->Stack.push((Some(label,vtx)),x)nxt)(out)initer(AdjSet.addlabelvis')nxtacc')inlet{acc=acc''';vis=vis''';_}=(bnxt(mkctx(prev)labelvis''acc''stopvtx))in(vis''',acc''')inlet(_,acc)=itervisitedstckinitinacc;;(** Get adjacency list of a node *)letadj_list_ofnodenodeMap=let{inc=incoming;out=outgoing;_}=NodeMap.findnodenodeMapinAdjSet.to_list(AdjSet.unionincomingoutgoing);;(** Get adjacency list of a node *)letadj_seq_ofnodenodeMap=let{inc=incoming;out=outgoing;_}=NodeMap.findnodenodeMapinAdjSet.to_seq(AdjSet.unionincomingoutgoing);;(** collect graph into a simple [(key, adj list), ...] *)letoutlistnodeMap=NodeMap.fold(funelt{out;_}acc->(elt,out)::acc)nodeMap[];;(* rev compat *)let_find_indexp=letrecauxi=function[]->None|a::l->ifpathenSomeielseaux(i+1)linaux0;;let_init_matrixsxsyf=letres=Array.makesx[||]in(* We must not evaluate [f x 0] when [sy <= 0]: *)ifsy>0thenbeginforx=0topredsxdoletrow=Array.makesy(fx0)infory=1topredsydoArray.unsafe_setrowy(fxy)done;Array.unsafe_setresxrowdone;end;res;;(** convert a graph to a 2d array and a key list defining the elt for each array index *)letadjmatrixnodeMap=letsz=NodeMap.cardinalnodeMapinletkeys=NodeMap.bindingsnodeMapin(* map outgoing edges to indices, return each elt and its index along
with the indexes of outgoing edges
{
"1":["2","3"]
"2":["1"]
"3":[]
....
}
becomes
idx node out
[
((0, "1"), [1,2]),
((1, "2"), [0]),
((2, "3"), []),
]
*)letadjs=List.mapi(funi(k,{out=o;_})->((i,k),AdjSet.fold(funeltacc->(* return the index of the outgoing edge *)Option.get(_find_index(fun(b,_)->equalbelt)keys)::acc)o[]))keysinletb=_init_matrixszsz(funrowcol->(* bool value of whether j is in the outgoing index *)Bool.to_int@@List.memcol(snd@@List.nthadjsrow))in(* return matrix and keys showing which elt is which index *)(b,List.map(fst)adjs);;(** convert a graph to 2d weights (edge values) and a key list defining the elt for each array index *)letwgtmatrixtransformdefvalnodeMap=letsz=NodeMap.cardinalnodeMapinletkeys=NodeMap.bindingsnodeMapin(* map each key to its index and list of outgoing indices *)let(adjs)=List.mapi(funi(k,{out=o;edg=wgt;_})->(* any outgoing node has to be in the keys list *)((i,k),AdjSet.fold(funeltacc->(* return the index of the outgoing edge *)(Option.get((_find_index(fun(b,_)->equalbelt)keys)),Vertex.edge2eltwgt)::acc)o[]))keysinletb=_init_matrixszsz(funij->(* bool value of whether j is in the outgoing index *)letv=List.find_opt(fun(idx,_)->idx=j)@@(snd@@List.nthadjsi)inmatchvwith|Some(_,msr)->transformmsr|_->defval)in(* return matrix and keys showing which elt is which index *)(b,List.map(fst)adjs);;(** create a graph from 2d adjacency matrix with keys defining which elt maps to what index *)letof_matrixnodeMatrixkeys=letg=List.fold_right(fun(_,e)a->addea)keysemptyinletindex=0inletadj_list=snd@@Array.fold_left(fun(idx,acc)iarr->letk=(snd(List.find(fun(id,_el)->id=idx)keys))inletindex'=0in(idx+1,(k,snd@@(Array.fold_left(fun(idx',acc')v->ifv=1then(idx'+1,(snd(List.find(fun(id,_el)->id=idx')keys))::acc')else(idx'+1,acc'))(index',[])iarr))::acc))(index,[])nodeMatrixinof_listadj_listg;;(** inout degree matrix along the diagonal - can be used to construct a
laplacian *)letdegmatrixnodeMap=letsz,adjs=NodeMap.fold(funkey{inc;out;_}(idx,acc)->(idx+1),(((idx,key),(AdjSet.cardinalinc+AdjSet.cardinalout))::acc))nodeMap(0,[])inletb=_init_matrixszsz(funij->ifi==jthensnd@@List.nthadjsielse0)in(* return matrix and keys showing which elt is which index *)(b,List.map(fst)adjs);;(** in and out degree for each elt *)letdegtabletblnodeMap=NodeMap.iter(funk{inc;out;_}->Hashtbl.addtblk(AdjSet.cardinalinc,AdjSet.cardinalout))nodeMap;;(** create a set of all edges *)letedgesetnodeMap=NodeMap.fold(funel{out=ou;_}a->AdjSet.fold(funnxa'->EdgeSet.add(el,nx)a')oua)nodeMapEdgeSet.empty;;(** create a sequence of edges *)letedgeseqnodeMap=(NodeMap.to_seqnodeMap)|>Seq.map(fun(el,{out=ou;_})->AdjSet.to_seqou|>Seq.map(funnx->(el,nx)))|>Seq.concat;;(** create a sequence of edges and weight values *)letedgewgtseqnodeMap=(NodeMap.to_seqnodeMap)|>Seq.map(fun(el,{out=ou;edg;_})->AdjSet.to_seqou|>Seq.map(funnx->(el,nx,Vertex.edge2nxedg)))|>Seq.concat;;(** like edgeset but returns the size to avoid recomputing if needed. returns
the node count, edge count and edge set in that order *)letedgesetsznodeMap=NodeMap.fold(funel{out=ou;_}(g,e,a)->lete',a'=AdjSet.fold(funnx(e',a')->e'+1,EdgeSet.add(el,nx)a')ou(e,a)ing+1,e',a')nodeMap(0,0,EdgeSet.empty);;(** graph -> incidence matrix, node array, elt array
useful in spectral clustering: https://math.stackexchange.com/a/3726603 *)letincmatrixnodeMap=letr,c,edgeset=(edgesetsznodeMap)inlet(t,_)=NodeMap.choosenodeMapinletidx=ref0inletnodes=Array.makertinletedges=Array.makec(t,t)inletbelongsi(f,t)=equalif||equalitinlet_=NodeMap.iter(funn_->let_=nodes.(!idx)<-ninincridx)nodeMapinlet_=idx:=0inlet_=EdgeSet.iter(fune->let_=edges.(!idx)<-einincridx)edgesetin_init_matrixrc(funr'c'->ifbelongsnodes.(r')edges.(c')then1else0),nodes,edges;;(** swap the incoming and outgoing edge direction - preserving edge weights
use transpose2 if you do not use edge weights! *)lettranspose(nodeMap:adjNodeMap.t)=NodeMap.map(fun{inc;out;lab;edg=wgts}->letwgts'=Weights.create(Weights.lengthwgts)inlet_=AdjSet.iter(funx->Weights.addwgts'x@@Weights.find(Vertex.weightsxnodeMap)lab)incin{inc=out;out=inc;lab;edg=wgts'})nodeMap;;(** swap the incoming and outgoing edge direction - WITHOUT preserving edge weights *)lettranspose2(nodeMap:adjNodeMap.t)=NodeMap.map(fun{inc;out;lab;edg=wgts}->{inc=out;out=inc;lab;edg=wgts})nodeMap;;(** toposort (happens-before) - assumes the graph is acyclic (DAG) otherwise the
result is wrong. *)lettoposortgraph=snd@@NodeMap.fold(funx_y(v,a)->ifAdjSet.memxvthen(v,a)elsedfs(fun_s->{swithstop=false})(fun_s->ifAdjSet.mems.elt(fsts.acc)thenselse{swithacc=AdjSet.adds.elt(fsts.acc),s.elt::(snds.acc)})graphx(v,a))graph(AdjSet.empty,[]);;(** whether edge from f to t exists in nodeMap *)lethas_edgeftnodeMap=AdjSet.memt(outgoingoffnodeMap);;(** acyclic if i cant form a cycle from each element *)letis_acyclicgraph=NodeMap.for_all(funx_y->not@@dfs(fun_s->letcyc=(AdjSet.memxs.vtx.out)in{swithacc=cyc;stop=cyc})(fun_->Fun.id)graphxfalse)graph;;(*************************************************************************
* Strongly connected Components *
* Every vertex is reachable in a SCC *
**************************************************************************)moduleScc=structtypesccnode={link:int;node:elt;indx:int};;moduleSccNode=structtypet=sccnodeletcompare=funxy->(Unique.comparex.nodey.node)letequal=fun{link=left;_}{link=right;_}->(Int.compareleftright)=0lethash=funx->x.linkendmoduleSccTbl:Hashtbl.Swithtypekey:=sccnode=Hashtbl.Make(SccNode)moduleSccSet:TSetwithtypet:=sccnode=TreeSet(SccNode)moduleSccMap:Map.Swithtypekey:=int=Map.Make(Int)lethasnodeex=equalx.nodee;;(** creates a Map of (ints -> ([idx...], Graph.t)) where the int is the link value.
it computes its neighbours into the list section of the Map value.
This makes more idiomatic to the outer graph structure which is also
a map albeit with different values
*)letinducednodeMapsccs=SccTbl.fold(fun{link=lowlink;_}eltacc->letedges=NodeMap.findeltnodeMapinlet{out;_}=edgesinletkeyseq=SccTbl.to_seq_keyssccsinletsccedg=(AdjSet.fold(funeac->matchSeq.find(hasnodee)keyseqwith|Somev->ifv.link!=lowlinkthenv.link::acelseac|None->ac)out[])inSccMap.updatelowlink(funnodeEl->matchnodeElwith|Some(l,v)->Some((l@sccedg),(NodeMap.addelt(edges)v))|None->Some(sccedg,NodeMap.singletonelt(edges)))acc)sccsSccMap.empty|>SccMap.map(fun(v,m)->(List.sort_uniq(Int.compare)v,m));;(** data holding the solution for strongly connected components *)typesolution={(* discovered nodes *)disc:sccnodeSccSet.set;(* visited nodes or nodes on the 'stck' of this solution *)onst:eltAdjSet.set;(* stacked nodes *)stck:sccnodelist;(* mapping from link values to nodes in an SCC *)sccs:eltSccTbl.t;(* discovery time *)time:int;};;typesccedge=(intlist*adjNodeMap.t);;letempty(size:int)={disc=SccSet.empty;onst=AdjSet.empty;stck=[];sccs=SccTbl.createsize;time=0};;letwhereisedgtarj=SccSet.find_first(hasnodeedg)tarj.disc;;letfindbypreddset=Option.is_some(SccSet.find_first_opt(pred)dset);;(** tarjans visit args: function graph root-node neighbour-node solution*)letvisitapplygrootvisitedtarj=ifnot(findby(hasnodevisited)tarj.disc)then(* unvisited edge - recurse and update *)lettarj'=applyvisitedgtarjinletuss'=whereisvisitedtarj'inletngbr=whereisroottarj'in{tarj'withdisc=(SccSet.add({ngbrwithlink=(minngbr.linkuss'.link)})tarj'.disc)}elseif(AdjSet.memvisitedtarj.onst)then(* the visited is on the stack *)(* update roots lowlink to visiteds discovery time index *)letngbr=whereis(root)tarjinletusss=whereis(visited)tarjin{tarjwithdisc=(SccSet.add({ngbrwithlink=(minngbr.linkusss.indx)})tarj.disc);}elsetarj;;(* How Tarjan builds an SCC until pred is met *)letrecpopsccpredtarjid=matchtarj.stckwith|sccel::rest->lettarj'={tarjwithstck=rest;onst=AdjSet.remove(sccel.node)tarj.onst;}inletx={sccelwithlink=id}inlet_=SccTbl.addtarj'.sccsxx.nodeinifpredsccelthentarj'elsepopsccpredtarj'id|[]->tarj;;letmksccnodentime={node=n;link=time;indx=time};;(* how tarjan creates a link for later use *)lettarjanaddns={swithdisc=(SccSet.addns.disc);onst=(AdjSet.addn.nodes.onst);stck=(n::s.stck);time=s.time+1};;(** Tarjans SCC algorithm
basically we should pop until we find our own
low-link when we started (lowlink = index) - this shows that it
is an SCC on backtrack - otherwise we keep pushing on to the stack
The stack is there to maintain an invariance over the visited nodes
during Depth first search
In the end remaining elements in the Stack are their own SCCs this way
All elements always belong to an SCC
*)lettarjangraph=letcount=ref0inletrecstrongconnectngs=(* mark as discovered and add onto the stack *)letr=(tarjanadd(mksccnodens.time)s)inletout=(outgoingofng)in(* recurse while adding *)lets'=AdjSet.fold(visit(strongconnect)gn)(out)rin(* get the low link of this node and compare to discovery time *)letngbr'=(whereis(n)s')inifngbr'.link=ngbr'.indxthenlet_=incrcountin(* pop elements into an scc until the elt is the same as ours *)popscc(hasnoden)s'!countelses'inNodeMap.fold(funelt_acc->iffindby(hasnodeelt)acc.discthenaccelse(strongconnecteltgraphacc))graph(empty8);;(* How Kosaraju builds an scc within the f (el) constraint *)letrecpopwhileftid=matcht.stckwith|el::rest->iffelthenlet_=SccTbl.addt.sccs{elwithlink=id}el.nodeinlett'={twithonst=AdjSet.addel.nodet.onst;stck=rest;}inpopwhileft'idelset|[]->t;;(* how kosaraju composes a solution for later use *)letkosaraddeltsccscc'=(* if already on the stack then already discovered again *)ifAdjSet.memeltscc'.onstthenscc'else(* not on the stack after recursion, stack and mark discovered *){scc'withstck=(mksccnodeeltscc.time)::scc.stck;onst=AdjSet.addeltscc.onst;};;(* Kosaraju visits all nodes until the first nodes with all edges going
back into the discovered or has none and builds a stack. After that
we transpose the graph and pop from the stack, seeing which elements
in the stack are still reachable on pop
time values are not really as relevant for this algorithm *)letkosarajugraph=letcount=ref0inletreciternode{out;_}scc=letscc''=(* If already on the stack it has been discovered *)ifAdjSet.memnodescc.onstthensccelse(* If all out edges are a subset of the discovered elements *)ifAdjSet.for_all(funel->findby(hasnodeel)scc.disc)(out)then(* stack it and move on *){sccwithstck=(mksccnodenodescc.time)::scc.stck;onst=AdjSet.addnodescc.onst;}else(* Mark this node as discovered at this time *)letnvst={sccwithdisc=SccSet.add(mksccnodenodescc.time)scc.disc}in(* Get the subset of undiscovered elements for recurse *)letdiff=AdjSet.filter(funoe->not(findby(hasnodeoe)nvst.disc))outinAdjSet.fold(funeltstate->iterelt(NodeMap.findeltgraph)state|>kosaraddeltscc)(diff)(nvst)in(* If already on the stack after first recursion then ok *)kosaraddnodescc''scc''inletfscc=NodeMap.fold(iter)graph(empty8)in(* transpose the graph *)lettgraph=transpose2graphinletiter2sccsccnode=(* if onst the we consider it already a part of an scc *)ifAdjSet.memsccnode.nodescc.onstthensccelselet_=incrcountin(* find all reachable nodes *)letvstd=dfs(fun_->Fun.id)(fun_s'->{s'withacc=s'.vis})tgraphsccnode.node(AdjSet.empty)in(* popelements into an scc while they are visited *)popwhile(fune->AdjSet.meme.nodevstd)scc(!count)inList.fold_leftiter2{fsccwithonst=AdjSet.empty}fscc.stck;;end(*************************************************************************
* Clusters *
* Clique: Induced subgraph is fully connected every pair of distinct *
* vertices is* adjacent with distance 1 or k - paths outside the clique *
* seem to be allowed *
* *
* Club: A generalization of a clique, where a set of vertices induces a *
* subgraph with a diameter at most (s) - the diameter must remain within *
* the club! *
* *
* Clan: A subclass of an (n)-clique with diameter (n), which makes *
* it connected within the clan - part of the point is whether a clique is*
* restricted to be maximal. k-clans have to be k-cliques *
**************************************************************************)moduleCluster=struct(**
Bron–Kerbosch algorithm (Maximal Cliques)
Warning: Self edges can cause p to run into an infinite loop so we
have to filter them out with `xormutuals`
This implementation does not use pivoting, use bronkerbosch2 for
that depending on your graph (it does extra computations)
*)letbronkerboschgraph=(*
r: clique being built
p: candidate vertices
x: exclusion set (already processed) - ensures r is maximal
*)letrecbkrpxcqs=ifAdjSet.is_emptyp&&AdjSet.is_emptyxthen(r::cqs)elselet_,_,ncqs=AdjSet.fold(funv(np,nx,cqs')->letngb=(xormutualsvgraph)in(* r + {v} *)letbr=(AdjSet.addvr)in(* p intersect ngb *)letbp=(AdjSet.interngbnp)in(* x intersect ngb *)letbx=(AdjSet.interngbnx)in(* any new cliques *)letcqs''=bk(br)(bp)(bx)cqs'in((AdjSet.removevnp),(AdjSet.addvnx),cqs''))(p)(p,x,cqs)inncqsin(* collect all nodes as candidates *)letkeys=NodeMap.fold(funel_acc->AdjSet.addelacc)graphAdjSet.emptyinbk(AdjSet.empty)(keys)(AdjSet.empty)[];;(** Same as bronkerbosch but with pivoting - can be usefull in some
cases with a tradeoff in extra computation! *)letbronkerbosch2graph=(*
r: clique being built
p: candidate vertices
x: exclusion set (already processed) - ensures r is maximal
*)letrecbkrpxcqs=ifAdjSet.is_emptyp&&AdjSet.is_emptyxthen(r::cqs)elselet_,_,ncqs=letu=AdjSet.choose(AdjSet.unionpx)inAdjSet.fold(funv(np,nx,cqs')->letngb=(xormutualsvgraph)in(* r + {v} *)letbr=(AdjSet.addvr)in(* p intersect ngb *)letbp=(AdjSet.interngbnp)in(* x intersect ngb *)letbx=(AdjSet.interngbnx)in(* any new cliques *)letcqs''=bk(br)(bp)(bx)cqs'in((AdjSet.removevnp),(AdjSet.addvnx),cqs''))(AdjSet.diffp(xormutualsugraph))(p,x,cqs)inncqsin(* collect all nodes as candidates *)letkeys=NodeMap.fold(funel_acc->AdjSet.addelacc)graphAdjSet.emptyinbk(AdjSet.empty)(keys)(AdjSet.empty)[];;end(*************************************************************************
* Spanning Trees *
**************************************************************************)(** Minimum Spanning Tree algorithms *)moduleSpan(Measure:Measurablewithtypeedge=edge)=structletecompareee'=wcompare(Measure.compare)(Measure.measuree)(Measure.measuree')moduleEdgeDisj=MakeDisjointSet(structtypet=eltletequal=equallethash=Hashtbl.hashend)moduleEdgeHeap=MakeFibHeap(structtypet=(elt*elt*edge)typeorder=edgeletbind(_,_,e)=eletcompare(x,y,_)(x',y',_)=letxc=Unique.comparexx'inletyc=Unique.compareyy'inmatchxcwith|0->yc|x->x;;letorder=ecompareend)(** Total cost from summing all edges in the minimum spanning tree *)letcostmst=edgewgtseqmst|>Seq.fold_left(funacc(_,_,ed)->wapply(Measure.addacc)(Measure.measureed))(Measure.zero);;(** Prims minimum spanning tree algo
only works for only for undirected graphs!! *)letprimstartgraph=letvis=AdjSet.emptyinletreciternodevispheapmst=letout,wgts=outweightsnodegraphinletpheap'=AdjSet.fold(funelac->EdgeHeap.insert(node,el,(Vertex.edge2elwgts))ac)(AdjSet.diffoutvis)pheapinmatchEdgeHeap.extract_optpheap'with|Some((c,n,e),pheap'')->ifAdjSet.memnvisthenitern(AdjSet.addnodevis)pheap''mstelseletmst'=add_weightecn(ensurec@@ensurenmst)initern(AdjSet.addnode@@AdjSet.addnvis)pheap''mst'|_->mstiniterstartvisEdgeHeap.emptyempty;;(** kruskals minimum spanning tree using disjoint set: connect describes
how you add edges into the new graph. assumes the graph has no
disconnected nodes *)letkruskal?(connect=add_weight)graph=letnodeseq=(Seq.map(fst)@@NodeMap.to_seqgraph)inletwgts=EdgeDisj.create(cardinalgraph)nodeseqinedgewgtseqgraph|>List.of_seq|>List.fast_sort(fun(_,_,x)(_,_,y)->ecomparexy)|>List.fold_left(funacc(cur,nxt,edj)->ifEdgeDisj.findcurwgts=EdgeDisj.findnxtwgtsthenaccelselet_=EdgeDisj.unionwgtscurnxtinconnectedjcurnxt(ensurecur@@ensurenxtacc))empty;;end(*************************************************************************
* Path Algos *
**************************************************************************)modulePath=structtype'cpathelt={from:elt;next:elt;via:elt;value:'c;}letmkpathftcost={from=f;next=t;via=f;value=cost;};;letviapathfvtcost={from=f;next=t;via=v;value=cost;};;moduleCompute(Measure:Measurablewithtypet=edgeandtypeedge=edge)=structtypemeasure=Measure.twrap(* implement an ordinal interface with the measure compare for the
heap order of paths (edges in the graph) *)modulePathList:Ordinalwithtypet=measurepatheltandtypeorder=measure=structtypet=measurepathelttypeorder=measureletbindt=t.valueletcomparelr=matchUnique.comparel.nextr.nextwith|0->Unique.comparel.fromr.from|x->x;;letorderlr=wcompare(Measure.compare)lrendmodulePathHeap=MakeFibHeap(PathList)modulePathSet=TreeSet(PathList)letshorterpathtoep=PathSet.find_first_opt(fune'->equale'.nexte)p(*
resolve djikstra path by skipping over non-interlinked nodes
e.g. for a sample output path
[("G", "E", `Val 7.); ("S", "A", `Val 7.); ("B", "D", `Val 6.);
("H", "F", `Val 6.); ("B", "A", `Val 5.); ("H", "G", `Val 5.);
("C", "L", `Val 5.); ("S", "C", `Val 3.); ("B", "H", `Val 3.);
("S", "B", `Val 2.); ("S", "S", `Val 0.)]
we interlink on edges and reverse the path
[("S", "S", `Val 0.); ("S", "B", `Val 2.); ("B", "H", `Val 3.);
("H", "G", `Val 5.); ("G", "E", `Val 7.)]
due to commonality of the from origin
*)letrecdijkresolvetargetacc=function|[]->acc|(from,next,value)::rest->ifequaltargetnextthen(dijkresolve[@tailcall])from((next,value)::acc)restelse(dijkresolve[@tailcall])targetaccrest;;(** Single source shortest path implementation of dijkstra *)letdijkstrastarttargetgraph=(* we take the path to ourselves as 0 *)letstartp=(viapathstartstartstart(`ValMeasure.zero))in(*
Set all start to out edges to infinity except the pseudo edge to
self to denote the start point of dijkstra
caveat: this may take longer but does not duplicate nodes hence
save some memory. Tradeoff for dense graphs
*)letinit=NodeMap.fold(funk_acc->(PathHeap.insert(viapathstartstartk`Inf)acc))graph(PathHeap.singletonstartp)inletreciterpsheapelp=ifPathHeap.is_emptyheapthenelpelselet(u,rest)=PathHeap.extractheapinifequalu.nexttargetthen((u.via,u.next,u.value)::elp)elselet(out,w)=outweightsu.nextgraphinlet(ps'',h')=AdjSet.fold(fune(p,a)->(* get distance from u to e *)letd=Measure.measure(Vertex.edge2ew)in(* add to distance from start to u *)letalt=wbind(Measure.add)u.valuedin(* demarkate edge with distance from start *)letpe=viapathstartu.nextealtin(* Relaxation *)matchshorterpathtoepswith|Somev->(* If alternative path is shorter than
previous we 'override it' in the set and
decrease the value in the heap *)ifwcompare(Measure.compare)altv.value=-1then(PathSet.addpep,PathHeap.decreasepepea)else(p,a)|None->(* First sighting *)(PathSet.addpep,PathHeap.decreasepepea))out(ps,rest)initerps''h'((u.via,u.next,u.value)::elp)indijkresolvetarget[]@@iter(PathSet.singletonstartp)init[];;(** shortest path with heuristics called on a node to align dijkstra outputs *)letastarheuristicstarttargetgraph=(* we take the path to ourselves as 0 *)letstartp=(mkpathstartstart(`ValMeasure.zero))in(*
Set all start to out edges to infinity except the pseudo edge to
self to denote the start point of dijkstra
omitted as can be implied when we use insert instead of decrease operation
init |> NodeMap.fold (fun k _v acc ->
(PathHeap.insert (mkpath start k `Inf) acc)
) graph
ideally the heuristic reduces the number of duplicates so we can
use a second (log n) insert
*)letinit=(PathHeap.singletonstartp)inletreciterpsheapelp=ifPathHeap.is_emptyheapthenelpelselet(u,rest)=PathHeap.extractheapinifequalu.nexttargetthen((u.from,u.next,u.value)::elp)elselet(out,w)=outweightsu.nextgraphinlet(ps'',h')=AdjSet.fold(fune(p,a)->(* get distance from u to e *)letd=Measure.measure(Vertex.edge2ew)in(* add to distance from start to u *)letalt=wbind(Measure.add)u.valuedinletalt'=wbind(Measure.add)alt(heuristice)in(* demarkate edge with distance from start
accounting for the heuristic cost *)letpe=mkpathu.nextealt'inmatchshorterpathtoepswith|Somev->(* If alternative path is shorter than
previous we 'override it' *)ifwcompare(Measure.compare)alt'v.value=-1then(PathSet.addpep,PathHeap.decreasepepea)else(p,a)|None->(* First sighting - insert instead of
decrease with dup *)(PathSet.addpep,PathHeap.insertpea))out(ps,rest)initerps''h'((u.from,u.next,u.value)::elp)indijkresolvetarget[]@@iter(PathSet.singletonstartp)init[];;(* similar idea to dijkstraresolve *)letbellresolvestarttargetnodes=ifPathSet.is_emptynodesthen[]elseletrecitertargetcycleacc=let{from;next=nxt;value;_}=PathSet.find_first(fun{from;_}->equalfromtarget)nodesinifequalstartfromthen(from,value)::accelseifequalstartnxtthenlet{from=from';value=value';_}=PathSet.find_first(fun{from;_}->equalfromstart)nodesin(from',value')::((from,value)::acc)elseifEdgeSet.mem(from,nxt)cyclethenfailwith"cycle in resolution"elseiternxt(EdgeSet.add(from,nxt)cycle)((from,value)::acc)initertargetEdgeSet.empty[];;(* how bellman ford updates values *)letbelliterbelltablemaxszgraph=letrecitersweep=ifsweep=maxszthen()elselet_=NodeMap.iter(funelt{inc;out;edg=wgts;_}->let(_,sofar,_)=Hashtbl.findbelltableeltin(* All incoming edges *)letsofar''=AdjSet.fold(funprvsofar'->let(_,value,wgts')=Hashtbl.findbelltableprvinletcost=Vertex.edge2eltwgts'|>Measure.measureinletvalue'=wbind(Measure.add)costvaluein(* relaxation *)ifwcompare(Measure.compare)value'sofar'=-1thenlet_=Hashtbl.replacebelltableelt(Someprv,value',wgts)invalue'elsesofar')incsofarin(* All outgoing edges *)let_=AdjSet.fold(funnxtsofar''->let(_,value,wgts')=Hashtbl.findbelltablenxtinletcost=Vertex.edge2nxtwgts|>Measure.measureinletvalue'=wbind(Measure.add)costsofar''in(* relaxation *)ifwcompare(Measure.compare)value'value=-1thenlet_=Hashtbl.replacebelltablenxt(Someelt,value',wgts')insofar''elsesofar'')outsofar''in())graphiniter(sweep+1)inlet_=iter0in();;(**
the Bellman-Ford algorithm can handle directed and undirected graphs with non-negative weights
it can only handle directed graphs with negative weights, as long as we don't have negative cycles
If there is a negative cycle we return it as part of the EdgeSet
*)letbellmanfordstarttarget(graph:adjNodeMap.t)=(* Set initial distance at `Inf except start *)let(sz,lst)=(NodeMap.fold(funk{edg=w;_}(acc,lst)->ifequalkstartthenacc+1,(k,(None,`ValMeasure.zero,w))::lstelseacc+1,(k,(None,`Inf,w))::lst)graph(0,[]))in(* Hashtbl for tracking predecessors *)letbelltable=Hashtbl.createszinlet_=List.iter(fun(k,v)->Hashtbl.addbelltablekv)lstin(* upper bound of n - 1 sweeps for convergence *)letmaxsz=sz-1inlet_=belliterbelltablemaxszgraphin(* Do an extra iteration to detect a negative cycle by marking
edges that relax as part of a negative cycle
*)letnegcycles=NodeMap.fold(funelt{inc;out;edg=wgts;_}acc->let(_,sofar,_)=Hashtbl.findbelltableeltinlet(sofar'',acc')=AdjSet.fold(funprv(sofar',acc')->let(_,value,wgts')=Hashtbl.findbelltableprvinletcost=Vertex.edge2eltwgts'|>Measure.measureinletvalue'=wbind(Measure.add)costvaluein(* relaxation *)ifwcompare(Measure.compare)value'sofar'=-1thenlet_=Hashtbl.replacebelltableelt(Someprv,value',wgts)in(value',EdgeSet.add(prv,elt)acc')else(sofar',acc'))inc(sofar,acc)in(* All outgoing edges *)let(_,acc'')=AdjSet.fold(funnxt(sofar'',acc'')->let(_,value,wgts')=Hashtbl.findbelltablenxtinletcost=Vertex.edge2nxtwgts|>Measure.measureinletvalue'=wbind(Measure.add)costsofar''in(* relaxation *)ifwcompare(Measure.compare)value'value=-1thenlet_=Hashtbl.replacebelltablenxt(Someelt,value',wgts')in(sofar'',EdgeSet.add(elt,nxt)acc'')else(sofar'',acc''))out(sofar'',acc')inacc'')graphEdgeSet.emptyinletpl=PathSet.of_seq@@Seq.map(fun(k,(p,v,_))->letp'=Option.value~default:kpinmkpathkp'v)@@Hashtbl.to_seqbelltablein(bellresolvestarttargetpl,negcycles);;letfloydwresolvestarttargetdistpredmap=(* decode node indices *)letdecodeidx=snd@@List.nthmapidxinlet*sidx=List.find_opt(fun(_i,n)->equalnstart)mapinlet*tidx=List.find_opt(fun(_i,n)->equalntarget)mapinlets,e=fstsidx,fsttidxinletreciteratpath=ifnot(at=s)thenletd=dist.(s).(at)inletat'=pred.(s).(at)inifwcompare(Int.compare)at'`Nan=0then(* denote a negative cycle! *)Some((target,`Nan)::path)elseletv=decodeatinletc'=wapply(Fun.id)at'initerc'((v,d)::path)elseSome((start,`ValMeasure.zero)::path)initere[];;(** All pairs shortest path via Floyd Warshall using a matrix, negative cycles are not detected by
default *)letfloydwarshall?(negcycles=false)(graph:adjNodeMap.t)=(* get distance matrix with non-existent edges at infinity *)letdist,map=wgtmatrix(funx->`Valx)`Infgraphinletsz=List.lengthmapin(* reconstruction matrix should point to i by default or null *)letnext=_init_matrixszsz(funij->ifwcompare(Measure.compare)dist.(i).(j)`Inf!=0then`Valielse`Nan)inletidx=sz-1inlet_=(fork=0toidxdofori=0toidxdoforj=0toidxdoletij=dist.(i).(j)inletik=dist.(i).(k)inletkj=dist.(k).(j)inletik_kj=wbind(Measure.add)ikkjin(* min (i->j), ((i->k at k') + (k->j at k')) *)ifwcompare(Measure.compare)ik_kjij=-1thenlet_=next.(i).(j)<-next.(k).(j)indist.(i).(j)<-ik_kjdonedonedone)inlet_=ifnotnegcyclesthen()else(* extra iterations which check if a path is improved to
detect a negative cycle similar to bellmanford *)(fork=0toidxdofori=0toidxdoforj=0toidxdoletik=dist.(i).(k)inletkj=dist.(k).(j)inletik_kj=wbind(Measure.add)ikkjinletik_kj'=dist.(i).(j)in(* compare widist.th current calculation *)ifwcompare(Measure.compare)ik_kjik_kj'=-1thenlet_=next.(i).(j)<-`NegInfindist.(i).(j)<-`NegInfdonedonedone)indist,next,map;;(** allpairs shortest paths best suited for sparse weighted directed graphs
if temp is equal to any value in the graph it will overwrite it
so it needs to be chosen carefully - we don't check for negative
cycles here!
If the graph had negative weights, it will be "reweighted" to
positive weights such that dijkstra can be applied!. This also*)letjohnsonstempgraph=(* temporary external source node *)letg',sz=NodeMap.fold(fune_(g,sz')->(add_weight(Measure.zero)tempeg,sz'+1))graph(addtempgraph,0)in(* also includes the temp vertex *)letht=Hashtbl.createszin(* assume all nodes start at infinity for bellman ford relaxation except temp *)let_=NodeMap.iter(funk{edg=w;_}->ifequalktempthenHashtbl.addhtk(None,`ValMeasure.zero,w)elseHashtbl.addhtk(None,`Inf,w))g'in(* reweighting with bellman ford *)let_=belliterht(sz-1)g'in(* reweight the graph *)letg''=NodeMap.map(fun{inc;out;lab=u;edg=wgt}->(* we need to copy the weights as they are globally mutable *)letwgt'=Weights.copywgtinlet(_,d_u,_)=Hashtbl.findhtuinlet_=AdjSet.iter(funv->let(_,d_v,_)=Hashtbl.findhtvinletw_uv=wbind(Measure.add)d_u(`Val(Vertex.edge2vwgt))inletw_uv'=wbind(Measure.sub)w_uvd_vinwapply(Vertex.updatevwgt')w_uv')outin{inc;out;lab=u;edg=wgt'})g'in(* A way to restore weights from f -> t *)letrestoreftedgev=let(_,h_v,_)=Hashtbl.findhttinlet(_,h_u,_)=Hashtbl.findhtfinwapply(Measure.addedgev)(wbind(Measure.sub)h_vh_u)in(g'',restore);;end(** construct an eulerian path *)lethierholzer?(endpoints=direulpath)graph=let*start,fin,size=endpointsgraphinlettbl=Hashtbl.createsizeinlet_=degtabletblgraphinletreciternodevispath=let(ileft,oleft)=Hashtbl.findtblnodeinifoleft=0then(vis,path)elseletout=outgoingofnodegraphinlet_=Hashtbl.replacetblnode(ileft,oleft-1)inlet(vis',path')=AdjSet.fold(funelt(vis',path')->ifEdgeSet.mem(node,elt)visthen(vis',path')elseiterelt(EdgeSet.add(node,elt)vis')path')out(vis,path)in(vis',node::path')inSome(snd@@iterstartEdgeSet.empty[fin]);;(** single source depth first walk until (f ctx -> bool) is true or all nodes
are exhausted. Requires the node edges to be weighted Uses simple
dfs to append edges and their corresponding weights to a list *)letnaivedfsgraphfstart=List.rev@@dfs(fun_s->(matchs.prevwith|Some(prev,_adj)->({swithstop=fs;acc={from=prev;next=s.elt;via=prev;value=(Vertex.edgeoprevs.eltgraph);}::s.acc})|None->{swithstop=fs;}))(Fun.constFun.id)graphstart[];;(** single source breadth first walk until (f ctx -> bool) is true or all nodes
are exhausted. Requires the node edges to be weighted. Uses simple
bfs to append edges and their corresponding weights to a list *)letnaivebfsgraphfstart=List.rev@@bfs(fun_qs->(matchs.prevwith|Some(prev,_adj)->{swithstop=fs;acc=({from=prev;next=s.elt;via=prev;value=(Vertex.edgeoprevs.eltgraph);}::s.acc)}|None->{swithstop=fs;}))(Fun.constFun.id)graphstart[];;end(** Graph flow algorithms *)moduleFlow(Measure:Measurablewithtypet=edgeandtypeedge=edge)=struct(* NB: Capacity is always non negative *)typemeasure=Measure.twraptypestate={mutableflow:edge}letmkstatef={flow=f}letzero=Measure.zero(* measure Captbl.t - holds current filled capacity *)moduleFlowtbl=Hashtbl.Make(structtypet=(elt*elt)letequal(x,y)(x',y')=matchUnique.comparexx'with|0->(Unique.compareyy')=0|_->falselethash=Hashtbl.hashend)(** dfs ford fulkerson - exists to grok the idea behind flow
pushes flow to the sink if there is any available capacity along a
chosen path until no more flow can be pushed. residual edges are
added so flow can be `pushed back` if there is capacity towards the
opposite direction. Path finding is the main addition here to ford
fulkerson
TODO: Capacity scaling or Dinics level graph heuristic ???
NB: This algo does not always terminate
*)letfordfulkerson?(maxit=Int.max_int)capsourcesinkgraph=(* if there are flow values already in the tbl we keep it - it can
reduce the total number of iterations since this algo depends on
Flow capacity *)let_=edgeseqgraph|>Seq.iter(fun(k,v)->matchFlowtbl.memcap(k,v),Flowtbl.memcap(v,k)with|(true,true)->()|(true,false)->Flowtbl.replacecap(v,k)(mkstatezero)|(false,true)->Flowtbl.replacecap(k,v)(mkstatezero)|_->let_=Flowtbl.replacecap(k,v)(mkstatezero)inFlowtbl.replacecap(v,k)(mkstatezero))in(* back edges need to be available *)letgraph'=NodeMap.fold(funelt{out;inc;_}acc->AdjSet.fold(funelt'acc'->(* if on incoming then assume edge already exists *)ifAdjSet.memelt'incthenacc'elseadd_weightMeasure.zeroelt'eltacc')outacc)graphgraphinletbreak=reffalseinletreciterprevnodeflowvispath=ifequalnodesinkthen(flow,AdjSet.addnodevis,Seq.cons(prev,sink)path)elseif!breakthen(flow,AdjSet.addnodevis,path)elselet{out;edg;_}=NodeMap.findnodegraph'in(* TODO: refactor to favor 'take_while' style *)let(flow',vis',path')=AdjSet.fold(funel(flow',vis',path')->if!break||AdjSet.memelvis'then(flow',vis',path')elseletmcap=Vertex.edge2eledginletcflw=Flowtbl.findcap(node,el)inletfavl=Measure.submcapcflw.flowinifMeasure.comparefavlMeasure.zero=1thenlet(flow'',vis'',path'')=iter(Some(node,mcap))el(`Valfavl)vis'path'in(* is there an augmenting path *)ifwcompare(Measure.compare)flow''(`ValMeasure.zero)=1thenlet_=break:=truein(flow'',vis'',path'')else(flow',vis'',path')else(flow',vis',path'))out(flow,(AdjSet.addnodevis),path)inif!breakthenletminflow=(wmin(Measure.compare)flowflow')in(minflow,vis',Seq.cons(prev,node)path')else(`ValMeasure.zero,vis',path')inletmaxiter=ref0in(* setup a loop *)letrecterminalmaxf=if!maxiter=maxitthenraiseNot_foundelse(* reset flow control *)let_=break:=falseinlet(x,_y,z)=(iterNonesource`InfAdjSet.emptySeq.empty)inlet_=Seq.iter(fun(f,t)->matchfwith|Some(f',_)->letfwd=Flowtbl.findcap(f',t)inletbwd=Flowtbl.findcap(t,f')inlet_=fwd.flow<-wapply(Measure.addfwd.flow)xinlet_=bwd.flow<-wapply(Measure.subbwd.flow)xin()|_->())zinifwcompare(Measure.compare)x(`ValMeasure.zero)=0thenmaxfelse(terminal[@tailcall])@@wbind(Measure.add)maxfxinterminal(`ValMeasure.zero);;(** Edmund karp - depends more on Edges and Vertices and not the
capacity values itself, mostyly like ford fulkerson.
tends to find shorter paths with bfs whereas dfs (in ford fulkerson above) can zigzag through
small capacity weights
*)letedmondskarp?(maxit=Int.max_int)capsourcesinkgraph=let_=edgeseqgraph|>Seq.iter(fun(k,v)->(* preserve flow value if they exist already *)matchFlowtbl.memcap(k,v),Flowtbl.memcap(v,k)with|(true,true)->()|(true,false)->Flowtbl.replacecap(v,k)(mkstatezero)|(false,true)->Flowtbl.replacecap(k,v)(mkstatezero)|_->let_=Flowtbl.replacecap(k,v)(mkstatezero)inFlowtbl.replacecap(v,k)(mkstatezero))inletque=Queue.create()in(* back edges (residual) need to be available *)letgraph'=NodeMap.fold(funelt{out;inc;_}acc->AdjSet.fold(funelt'acc'->ifAdjSet.memelt'incthenacc'elseadd_weightMeasure.zeroelt'eltacc')outacc)graphgraphinletbreak=reffalseinletfmin=wmin(Measure.compare)inletfcmp=wcompare(Measure.compare)inletreciterflowvispath=ifQueue.is_emptyquethen(`ValMeasure.zero,vis,[])elseletprev,cur=Queue.popqueinlet{out;edg;_}=vertexofcurgraph'in(* be careful not to add all paths as only some need to be considered *)letdiff=AdjSet.fold(funnxtvis->if!break||AdjSet.memnxtvisthenviselseletmcap=Vertex.edge2nxtedginletcflw=Flowtbl.findcap(cur,nxt)inletfavl=Measure.submcapcflw.flowinifMeasure.comparefavlMeasure.zero=1thenifequalnxtsinkthenlet_=break:=truein(AdjSet.addnxtvis)elselet_=Queue.add(Some(cur,favl),nxt)quein(AdjSet.addnxtvis)elsevis)outvisinif!breakthenletmcap=Vertex.edge2sinkedginletcflw=Flowtbl.findcap(cur,sink)inletfavl=Measure.submcapcflw.flowinletbtnk=fmin(`Valfavl)flowinletcpth=(Some(cur,favl))inletpath'=(prev,cur)::(cpth,sink)::pathinmatchprevwith|Some(_pelt,pcap)->(fmin(`Valpcap)btnk),(AdjSet.uniondiffvis),path'|None->btnk,(AdjSet.uniondiffvis),path'elseletf,v,p=iterflow(AdjSet.uniondiffvis)pathinif!breakthenmatchprevwith|Some(_pelt,pcap)->(fmin(`Valpcap)(fminfflow)),v,(prev,cur)::p|None->(fminfflow),v,(prev,cur)::pelse`ValMeasure.zero,v,pinletmaxiter=ref0in(* setup a loop *)letrecloopmaxf=if!maxiter=maxitthenraiseNot_foundelse(* reset flow control *)let_=break:=falseinlet_=incrmaxiterinlet_=Queue.clearqueinlet_=Queue.push(None,source)queinlet(x,_,z)=(iter`Inf(AdjSet.singletonsource)[])iniffcmpx(`ValMeasure.zero)=0thenmaxfelselet(_,_z')=List.fold_left(fun(sink',p)(f,t)->matchfwith|Some(f',_)->ifequalsink'tthenletfwd=Flowtbl.findcap(f',t)inletbwd=Flowtbl.findcap(t,f')inlet_=fwd.flow<-wapply(Measure.addfwd.flow)xinlet_=bwd.flow<-wapply(Measure.subbwd.flow)xin(f',sink'::p)else(sink',p)|_->(sink',sink'::p))(sink,[])(List.revz)inletmaxf'=wbind(Measure.add)maxfxin(loop[@tailcall])maxf'inloop(`ValMeasure.zero);;endmoduleMatching=struct(** halls theorem:
checks if all edges can be matched in a bipartite graph. pass in the
graph and one half of the bipartite graph
only really useful for small sets :-) as it generates
many subsets
you can use flow algorithms do find this match by adding dummy nodes
to the graph and adding "binary (0,1)" weights. weights that are 1
at the end of the flow will give the matching
*)lethallgraphtset=letbreak=reftrueinlet_=Seq.take_while(funtset'->let(crd,nbr)=(AdjSet.fold(funel(sm,ac)->sm+1,AdjSet.union(neighbourselgraph)ac)tset'(0,AdjSet.empty))inif(crd<=AdjSet.cardinalnbr)thentrueelselet_=break:=falseinfalse)(AdjSet.subset_seqtset)|>Seq.iter(ignore)in!break;;moduleCompute(Measure:Measurablewithtypet=edgeandtypeedge=edge)=structtyperank=(elt*Measure.t)moduleRankElt:Ordinalwithtypet=rankandtypeorder=Measure.t=structtypet=ranktypeorder=Measure.tletbindt=sndtletcomparelr=Unique.compare(fstl)(fstr)letorderlr=Measure.comparelrendmoduleRankHeap=MakeFibHeap(RankElt)(* Graph is modeled as a complete graph with edges representing
the rankings - higher values mean better rankings *)letgaleshapelygraphproposersacceptors=(* matching from acceptors to proposers *)letmatching=EdgeSet.emptyin(* max heap - higher ranked first meaning more preffered *)letcmp=RankHeap.maxifyinlet_=RankHeap.churn_threshold:=16inletproprank=AdjSet.fold(funelac->letranks=RankHeap.emptyinletwgts=Vertex.weightselgraphinletranks'=AdjSet.fold(funel'ac'->letrate=Vertex.edge2el'wgtsinRankHeap.insert~cmp:cmp(el',rate)ac')acceptorsranksin(el,ranks')::ac)proposers[]in(* closure over proprank for 'recalling' proposers for
rematching *)letrecallelt=List.find(fun(x,_)->equaleltx)proprankinletrecitermatchespropscomplete=matchpropswith|[]->matches|((prps,prnk)::rest)->(* our next choice
TODO: what if we run out of choices ?? *)let((acp,_),rem)=RankHeap.extract~cmp:cmpprnkinifAdjSet.memacpcompletethen(* Find the partner *)let(_,curp)=EdgeSet.find_first(fun(a,_)->equalaacp)matchesin(* how do they rank us against their current partner *)letwgt=Vertex.weightsacpgraphinletrnk=Vertex.edge2prpswgtinletcur=Vertex.edge2curpwgtin(* are we better than the proposer *)ifMeasure.comparecurrnk=1then(* prefers us *)letnewmtch=EdgeSet.add(acp,prps)(EdgeSet.remove(acp,curp)matches)in(* curp is now unmatched *)letnp=recallcurpinletsz=RankHeap.cardinal(sndnp)inlet_=Format.printf"recall capacity: %d\n"sziniternewmtch(np::rest)completeelse(* check remaining unmatched *)itermatches((prps,rem)::rest)completeelse(* match them and move on *)letmatches'=EdgeSet.add(acp,prps)matchesin(* cross them off our lists *)itermatches'rest(AdjSet.addacpcomplete)initermatchingproprankAdjSet.empty;;endendmoduleSerialize(Serde:SerDewithtypeedge:=edgeandtypeelt:=elt)=struct(* e.g. color = red ... *)moduleStyleTbl:Hashtbl.Swithtypekey=string=Hashtbl.Make(String)(* (string_of_elt elt) -> StylTbl *)moduleAttrbTbl:Hashtbl.Swithtypekey=string=Hashtbl.Make(String)(* (cluster_index) -> AttrbTbl *)moduleClstrTbl:Hashtbl.Swithtypekey=int=Hashtbl.Make(structtypet=intletequal=Int.equallethash=Hashtbl.hashend)(** export to csv *)letto_csvgraph=NodeMap.to_seqgraph|>Seq.map(fun(elt,{out;edg=wgt;_})->ifWeights.lengthwgt>0thenSeq.cons(fun()->Format.sprintf("\n%s,")(Serde.string_of_eltelt))(AdjSet.to_seqout|>Seq.map(funel->fun()->Format.sprintf("%s %s,")(Serde.string_of_eltel)(Serde.string_of_wgt(Vertex.edge2elwgt))))elseSeq.cons(fun()->Format.sprintf("\n%s,")(Serde.string_of_eltelt))(AdjSet.to_seqout|>Seq.map(funel->fun()->Format.sprintf("%s,")(Serde.string_of_eltel))));;(** export to dot *)letto_dot?(dir=false)?(sub=false)namegattrsnattrseattrsgraph=letheader=ifdirthen"digraph"else"graph"inletedglnk=ifdirthen"->"else"--"in(* can be reused in a subgraph *)letheader=ifsubthen"subgraph"elseheaderinletvisedg=refEdgeSet.emptyinSeq.cons(Seq.cons(fun()->Format.sprintf("%s %s {\n")headername)(* global attributes *)(StyleTbl.to_seqgattrs|>Seq.map(fun(k,v)->fun()->Format.sprintf("\t%s=\"%s\";\n")kv)))(Seq.append(NodeMap.to_seqgraph|>Seq.map(fun(elt,{out;edg=wgt;_})->(* no neighbours - check only for attributes *)leteltkey=(Serde.string_of_eltelt)inletweighted=Weights.lengthwgt>0inifAdjSet.is_emptyoutthenmatchAttrbTbl.find_optnattrseltkeywith(* Some node attributes *)|Someattrs->Seq.cons(fun()->Format.sprintf"\t%s\t["eltkey)(StyleTbl.to_seqattrs|>Seq.map(fun(k,v)->fun()->Format.sprintf"%s=%s, "kv)|>(Fun.flipSeq.append(Seq.return(fun()->"];\n"))))(* no node attributes or neighbours *)|_->Seq.cons(fun()->Format.sprintf"\t%s;\n"(Serde.string_of_eltelt))Seq.empty(* handle neighbours and attributes *)elseAdjSet.to_seqout(* handle edge attributes, edge key follows 'f-t' *)|>Seq.map(funx->letxs=(Serde.string_of_eltx)inletek,ev=(eltkey^"-"^xs,xs)inletdne=letvis=!visedginifEdgeSet.mem(x,elt)visthentrueelselet_=visedg:=(EdgeSet.add(elt,x)vis)infalseinifdnethen(fun()->"")elseletlabel=ifnotweightedthen""elseFormat.sprintf"label=\"%s\""(Serde.string_of_wgt@@Vertex.edge2xwgt)inmatchAttrbTbl.find_opteattrsekwith|Someiattr->fun()->letattrs=ifStyleTbl.lengthiattr=0then(ifweightedthen"\t[ "^label^" ]"elselabel)else(* all attributes *)(Seq.fold_left(^)"\t[ "((StyleTbl.to_seqiattr|>Seq.map(fun(k,v)->Format.sprintf"%s=\"%s\", "kv))|>(Fun.flipSeq.append(Seq.return(Format.sprintf"%s ]"label)))))inFormat.sprintf"\t%s %s %s %s;\n"eltkeyedglnkevattrs|None->fun()->Format.sprintf"\t%s %s %s\t[ %s ];\n"eltkeyedglnkevlabel)|>(Seq.append(Seq.return(fun()->matchAttrbTbl.find_optnattrseltkeywith(* Some node attributes *)|Someattrs->Seq.fold_left(^)(Format.sprintf"\t%s\t[ "eltkey)(StyleTbl.to_seqattrs|>Seq.map(fun(k,v)->Format.sprintf"%s=%s, "kv)|>(Fun.flipSeq.append(Seq.return" ];\n")))(* no node attributes or neighbours *)|_->Format.sprintf"\t%s;\n"(Serde.string_of_eltelt))))))(Seq.return(Seq.return(fun()->"}\n"))));;(** export a graph with subgraph clusters into dot format *)letto_dot_cluster?(dir=false)nameonnamegattrsclattrsnattrseattrsgraphs=letheader=ifdirthen"digraph"else"graph"inlettmpclstr=StyleTbl.create0inSeq.cons(Seq.cons(fun()->Format.sprintf("%s %s {\n")headername)(* global attributes *)(StyleTbl.to_seqgattrs|>Seq.map(fun(k,v)->fun()->Format.sprintf("\t%s=\"%s\";\n")kv)))(Seq.append(Scc.SccMap.to_seqgraphs|>Seq.map(fun(idx,(ngbrs,cluster))->letname=(onnameidxngbrs)inmatchClstrTbl.find_optclattrsidxwith|Someclattr->(to_dot~dir:dir~sub:truenameclattrnattrseattrscluster)|>Seq.concat|_->(to_dot~dir:dir~sub:truenametmpclstrnattrseattrscluster)|>Seq.concat))(Seq.return(Seq.return(fun()->"}\n"))));;end(* TODO:
Random:
Rodl nibble and Erdos-renyi
Blossom
Flow:
Goldberg Rao (with link cut tree)
Min Cost Flow || Min weight bipartite matching
Matching (Unweighted + Weigted)
Blossom algo (non bipartite)
Hopcroft Kraft (unweighted)
LP Network simplex
Vertex cover and dual problems
Planar:
Boyer-Myrovold
Euler formula: V - E + F = 2
Extra:
Ramsey and Moore graphs
Tree linearization and LCA | RMQ
Unordered Set and Splay Tree | Splay Maps as alternatives
Segment trees
Printers??
*)end;;