123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444(*********************************************************************************)(* Stog *)(* *)(* Copyright (C) 2012-2015 INRIA All rights reserved. *)(* Author: Maxence Guesdon, INRIA Saclay *)(* *)(* This program is free software; you can redistribute it and/or modify *)(* it under the terms of the GNU General Public License as *)(* published by the Free Software Foundation, version 3 of the License. *)(* *)(* This program 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. See the *)(* GNU General Public License for more details. *)(* *)(* You should have received a copy of the GNU General Public *)(* License along with this program; if not, write to the Free Software *)(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA *)(* 02111-1307 USA *)(* *)(* As a special exception, you have permission to link this program *)(* with the OCaml compiler and distribute executables, as long as you *)(* follow the requirements of the GNU GPL in regard to all of the *)(* software in the executable aside from the OCaml compiler. *)(* *)(* Contact: Maxence.Guesdon@inria.fr *)(* *)(*********************************************************************************)(** Structure de graphe creux. *)moduletypeGMap=sigtypekeytype'atvalcreate:unit->'atvalget:'at->key->'avalset:'at->key->'a->'atvalremove:'at->key->'atvalfold:(key->'a->'b->'b)->'at->'b->'bvaliter:(key->'a->unit)->'at->unitendmoduletypeS=sigtypettypekeytypeedge_datavalcreate:unit->tvalmarshal:t->stringvalunmarshal:string->tvalsucc:t->key->(key*edge_data)listvalpred:t->key->(key*edge_data)listvaladd:t->key*key*edge_data->tvalrem:t->key*key->(edge_data->bool)->tvalrem_all:t->key*key->tvalisolate:t->key->tvalremove_node:t->key->tvalpred_roots:?ignore_deps:edge_datalist->t->keylistvalsucc_roots:t->keylistvalrecursive_succs:t->?pred:(edge_data->bool)->key->keylistvalrecursive_preds:t->?pred:(edge_data->bool)->key->keylistvalreverse:t->tvalfold_succ:t->(key->(key*edge_data)list->'a->'a)->'a->'avalfold_pred:t->(key->(key*edge_data)list->'a->'a)->'a->'avaliter_succ:t->(key->(key*edge_data)list->unit)->unitvaliter_pred:t->(key->(key*edge_data)list->unit)->unitvaldot_of_graph:?f_edge:(edge_data->string*(string*string)list)->f_node:(key->string*string*(string*string)list)->t->stringvalnodes_by_pred_order:t->keylistvalshortest_path:t->(t->key*key->(float*edge_data)option)->key*key->(key*edge_data*key)listend;;(**
Notre module {!Graph} permet la construction et la manipulation de graphes creux,
avec la possibilit d'annoter chaque arc reliant deux sommets par un
type donn par le module le paramtre [Edge].
*)moduleMake(M:GMap)(Edge:Map.OrderedType)=struct(** Pour reprsenter un graphe, nous utilisons deux "maps", l'un pour
avoir rapidement les successeurs d'un sommet, l'autre pour avoir rapidement
ses prdcesseurs.
La structure de ces "maps" est dfinie par le module en paramtre.
A chaque indice des deux maps, nous avons donc respectivement
la liste des sucesseurs et des prdcesseurs du sommet correspondant cet
indice.
Les listes des successeurs et prdcesseurs sont des listes de paires
[(identifiant du sommet, donne d'annotation)]. Quand on ajoute un arc [i -> j],
il est en fait ajout une fois dans la liste des successeurs de [i] et une
fois dans la liste des prdcesseurs de [j]. Les donnes d'annotation sont donc
en double. Il faut donc veiller ce qu'elles ne soient pas trop grosses et
prfrer au besoin l'utilisation d'un identifiant dans une autre structure.
Le module [Edge] permet d'indiquer le type des annotations.
Ainsi, on peut comparer les donnes qui annotent les arcs, pour pouvoir supprimer
par exemple un arc entre deux sommets et correspondant une annotation, sans
supprimer un autre arc entre ces deux mmes sommets mais ayant une autre annotation.
*)typekey=M.keytypeedge_data=Edge.ttypet={succ:(M.key*edge_data)listM.t;(** successors of a node *)pred:(M.key*edge_data)listM.t;(** predecessors of a node *)};;letcreate()={succ=M.create();pred=M.create();};;letmarshalt=Marshal.to_stringt.succ[]letunmarshals=letsucc=Marshal.from_strings0inletadd_onedstmap(src,data)=tryletl=M.getmapsrcinM.setmapsrc((dst,data)::l)withNot_found->M.setmapsrc[dst,data]inletadd_listkeysuccsmap=List.fold_left(add_onekey)mapsuccsinletpred=M.foldadd_listsucc(M.create())in{succ=succ;pred=pred;}(** Les accs aux successeurs et prdcesseurs se font l'aide des fonctions suivantes,
et sont obtenus sous la forme de liste de paires
[(identifiant du successeur/prdcesseur, donne d'annotation de l'arc)].
*)letsuccgkey=tryM.getg.succkeywithNot_found->[];;letpredgkey=tryM.getg.predkeywithNot_found->[];;(** L'ajout dans un graphe se fait en utilisant la fonction {!add} et en prcisant
le graphe et un triplet [(sommet source, sommet destination, donne d'annotation)].
Si le mme arc est ajout deux fois avec la mme annotation, le deuxime
ajout est ignor (utilisation de la fonction de comparaison pour le dterminer).
*)letaddg(i,j,data)=(* make sure that i appears in pred and j in succ *)letnew_succ=tryignore(M.getg.succj);g.succwithNot_found->M.setg.succj[]inletnew_pred=tryignore(M.getg.predi);g.predwithNot_found->M.setg.predi[]inletg={succ=new_succ;pred=new_pred}inletsucc=letsuccs=succgiinifnot(List.exists(fun(k,d)->k=j&&Edge.compareddata=0)succs)thenM.setg.succi((j,data)::succs)elseg.succinletpred=letpreds=predgjinifnot(List.exists(fun(k,d)->k=i&&Edge.compareddata=0)preds)thenM.setg.predj((i,data)::preds)elseg.predin{succ=succ;pred=pred}(** Pour supprimer un arc parmi d'autres entre deux sommets [i] et [j], on utilise la
fonction {!rem} avec une fonction de prdicat prenant en paramtre une annotation
et qui renvoie [true] si l'arc en question doit tre supprim.
*)letremg(i,j)predic=letsucc=M.setg.succi(List.filter(fun(k,d)->k<>j||not(predicd))(succgi))inletpred=M.setg.predj(List.filter(fun(k,d)->k<>i||not(predicd))(predgj))in{succ=succ;pred=pred};;(** Il est galement possible de supprimer tous les arcs entre deux sommets [i] et [j],
avec la fonction {!rem_all}. Cela revient utiliser {!rem} avec un prdicat
retournant toujours [true]. *)letrem_allg(i,j)=remg(i,j)(fun_->true);;(** Isole le sommet indiqu en supprimant tous les arcs qui l'ont pour source ou pour
destination. *)letisolategi=letg=List.fold_right(fun(j,_)g->rem_allg(i,j))(succgi)ginList.fold_right(fun(j,_)g->rem_allg(j,i))(predgi)g;;letremove_nodegi=letg=isolategiinletnew_succ=M.removeg.succiinletnew_pred=M.removeg.prediin{succ=new_succ;pred=new_pred}(** Il est possible d'obtenir les "racines" du graphe, soit en tant que prdcesseurs
(ce sont les sommets n'ayant pas de prcdesseurs et prcdant donc tous les autres
sommets), soit en tant que successeurs (ce sont les sommets qui n'ont pas de
successeurs), respectivement avec les fonctions {!pred_roots} et {!succ_roots}.
@param le paramtre ignore depends permet d'indiquer des types d'arcs ignorer
pour le calcul.
*)letpred_roots?(ignore_deps=[])g=matchignore_depswith[]->M.fold(funkeylacc->matchlwith[]->key::acc|_->acc)g.pred[]|deps->letpred_edge(_,dep)=not(List.memdepdeps)inletprededges=not(List.existspred_edgeedges)inM.fold(funkeylacc->ifpredlthenkey::accelseacc)g.pred[]letsucc_rootsg=M.fold(funkeylacc->matchlwith[]->key::acc|_->acc)g.succ[];;(** La fonction {!reverse} permet de changer le sens des arcs, les successeurs devenant
prdcesseurs et rciproquement. Attention, les donnes d'annotation restent inchanges. *)letreverseg={pred=g.succ;succ=g.pred};;letfold_succgf=M.foldfg.succ;;letfold_predgf=M.foldfg.pred;;(** Deux fonctions de convenance existent pour appliquer une fonction chaque sommet et
respectivement tous ses successeurs ou tous ses prdcesseurs:
{!iter_succ} et {!iter_pred}. *)letiter_succgf=M.iterfg.succ;;letiter_predgf=M.iterfg.pred;;(** Il est possible d'imprimer le graphe au format {{:http://www.graphviz.org}Graphviz},
en utilisant la fonction {!dot_of_graph}.
@param f_edge permet d'indiquer quelle chane de caractres utiliser comme label
pour une annotation d'arc.
@param f_node permet d'indiquer quelle chane de caractres utiliser comme label
pour un sommet.
*)letdot_of_graph?(f_edge:(Edge.t->string*(string*string)list)option)~(f_node:(M.key->string*string*(string*string)list))(graph:t)=letb=Buffer.create512inletatts_of_node=funx->let(_,label,atts)=f_nodexin("label",label)::attsinletatts_of_edge=matchf_edgewithNone->(fun_->[])|Somef->funx->let(label,atts)=fxin("label",label)::attsinBuffer.add_stringb("digraph G {ratio=auto;\n"^"margin=\"0.1,0.1\";\n");letstring_of_att(s1,s2)=Printf.sprintf"%s=\"%s\""s1s2inletstring_of_atts=function[]->""|atts->Printf.sprintf"[%s]"(String.concat","(List.mapstring_of_attatts))inletmoduleS=Set.Make(structtypet=M.keyletcompare=Stdlib.compareend)inletprinted=refS.emptyinletprint_if_not_yetnode_id=ifnot(S.memnode_id!printed)thenbeginlet(nid,_,_)=f_nodenode_idinPrintf.bprintfb"%s %s;\n"nid(string_of_atts(atts_of_nodenode_id));printed:=S.addnode_id!printedendinletfnode_idsuccs=print_if_not_yetnode_id;let(nid,_,_)=f_nodenode_idinList.iter(fun(id,data)->print_if_not_yetid;let(id,_,_)=f_nodeidinletatts=atts_of_edgedatainPrintf.bprintfb"%s -> %s %s;\n"nidid(string_of_attsatts);)succsiniter_succgraphf;Buffer.add_stringb"}\n";Buffer.contentsb;;(** La fonction {!nodes_by_pred_order} permet de retourner une liste des sommets
dans leur ordre (partiel) de prcdence. *)letnodes_by_pred_orderg=letrecitergacc=matchpred_rootsgwith[]->List.revacc|i::_->iter(remove_nodegi)(i::acc)initerg[];;letrecursive_nextget_next=letmoduleS=Set.Make(structtypet=M.keyletcompare=Stdlib.compareend)inletfilterpredl=List.filter(fun(_,edge)->prededge)linletreciterg?(pred=(fun_->true))accid=letnext=get_nextgidinlet_next=filterprednextinList.fold_left(funacc(next_id,_)->ifS.memnext_idaccthenaccelseiterg~pred(S.addnext_idacc)next_id)accnextinfung?predid->S.elements(iterg?predS.emptyid)letrecursive_succs=recursive_nextsuccletrecursive_preds=recursive_nextpred(** La fonction {!shortest_path} calcule le plus court chemin entre deux sommets
[s] et [d], d'aprs une fonction de cot en paramtre.
La fonction de cot doit retourner une valeur strictement positive ainsi
que l'annotation de l'arc utilis pour avoir cette valeur (il est possible
d'avoir des cots diffrents entre deux sommets s'il y a plusieurs arcs entre
ces deux sommets). La fonction de cot retourne [None] s'il n'est pas possible
d'aller d'un sommet donn un autre (les deux sommets ne sont pas connects).
L'algorithme utilis est celui de
{{:http://tide4javascript.com/?s=Dijkstra}Djikstra}.
*)letshortest_pathgcost(s,d)=letp=M.fold(funi_p->M.setpi(ifi=sthen0.0elseinfinity))g.succ(M.create())inletv_done=M.fold(funi_acc->M.setaccifalse)g.succ(M.create())inletv_pred=M.fold(funi_acc->M.setacciNone)g.succ(M.create())inlet(_,_,v_pred)=M.fold(funv_(p,v_done,v_pred)->matchpredgvwith[]->(* do not take this vertice into account any more *)(p,v_done,v_pred)|_->let(_mindist,closest)=M.fold(funi_(mindist,closest)->if(not(M.getv_donei))&&M.getpi<mindistthen(M.getpi,Somei)else(mindist,closest))g.succ(infinity,None)inmatchclosestwithNone->(p,v_done,v_pred)(* FIXME ? this means accepting isolated vertices
insteaf of
raise Not_found*)|Someclosest->letv_done=M.setv_doneclosesttrueinlet(p,v_pred)=M.fold(funi_(p,v_pred)->ifnot(M.getv_donei)thenmatchcostg(closest,i)with|None->(p,v_pred)|Some(w,edge_data)->if(M.getpclosest)+.w<M.getpithen(M.setpi(M.getpclosest+.w),M.setv_predi(Some(closest,edge_data)))else(p,v_pred)else(p,v_pred))g.succ(p,v_pred)in(p,v_done,v_pred))g.succ(p,v_done,v_pred)inletrecbuild_pathaccv=matchM.getv_predvwithNone->acc|Some(v2,edge_data)->build_path((v2,edge_data,v)::acc)v2inletpath=build_path[]dinmatchpathwith[]->raiseNot_found|_->(* let s_path = String.concat " -> " (List.map (fun (_,_,j) -> string_of_int j) path) in
prerr_endline (Printf.sprintf "shortest path %d -> %d = %d -> %s" s d s s_path);
*)pathendmoduleMake_with_map(P:Map.OrderedType)(Edge:Map.OrderedType)=Make(structmoduleM=Map.Make(P)typekey=M.keytype'at='aM.tletcreate()=M.emptyletgettk=M.findktletsettkv=M.addkvtletremovetk=M.removektletfold=M.foldletiter=M.iterend)(Edge);;