···1122+ - [Oper] fixed transitive reduction (#145, reported by sim642)
33+ and tests for transitive reduction!
44+ - new example `depend2dot` to turn `make`-like dependencies
55+ into a DOT graph, with transitive reduction
26 - [Graphviz]: added `PosPinned` to type `NeatoAttributes.vertex`
37 - [Oper]: improved efficiency of `intersect`
48 (#136, reported by Ion Chirica)
+65
examples/depend2dot.ml
···11+(**************************************************************************)
22+(* *)
33+(* Ocamlgraph: a generic graph library for OCaml *)
44+(* Copyright (C) 2004-2007 *)
55+(* Sylvain Conchon, Jean-Christophe Filliatre and Julien Signoles *)
66+(* *)
77+(* This software is free software; you can redistribute it and/or *)
88+(* modify it under the terms of the GNU Library General Public *)
99+(* License version 2, with the special exception on linking *)
1010+(* described in file LICENSE. *)
1111+(* *)
1212+(* This software is distributed in the hope that it will be useful, *)
1313+(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
1414+(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *)
1515+(* *)
1616+(**************************************************************************)
1717+1818+open Graph
1919+2020+let usage () =
2121+ Format.eprintf "usage: depend2dot@.";
2222+ Format.eprintf "reads a dependency graph on the standard input, in format@.";
2323+ Format.eprintf " a: b c d@.";
2424+ Format.eprintf " b: c e@.";
2525+ Format.eprintf " etc.@.";
2626+ Format.eprintf "and prints a reduced graph in DOT format on the standard output.@.";
2727+ exit 1
2828+2929+module G = Imperative.Digraph.Abstract(String)
3030+module O = Oper.Make(Builder.I(G))
3131+module H = Hashtbl
3232+3333+let graph = G.create ()
3434+3535+let () =
3636+ let nodes = H.create 16 in
3737+ let node s = try H.find nodes s
3838+ with Not_found -> let v = G.V.create s in H.add nodes s v; v in
3939+ let node s = node (String.trim s) in
4040+ let add v w = if w <> "" then G.add_edge graph (node v) (node w) in
4141+ let add v w = add v w in
4242+ let parse_line s = match String.split_on_char ':' s with
4343+ | [v; deps] -> List.iter (add v) (String.split_on_char ' ' deps)
4444+ | [_] -> ()
4545+ | _ -> usage () in
4646+ let rec read () = match read_line () with
4747+ | s -> parse_line s; read ()
4848+ | exception End_of_file -> () in
4949+ read ()
5050+5151+let graph = O.replace_by_transitive_reduction graph
5252+5353+module Display = struct
5454+ include G
5555+ let vertex_name = V.label
5656+ let graph_attributes _ = []
5757+ let default_vertex_attributes _ = []
5858+ let vertex_attributes _ = []
5959+ let default_edge_attributes _ = []
6060+ let edge_attributes _ = []
6161+ let get_subgraph _ = None
6262+end
6363+module Dot = Graphviz.Dot(Display)
6464+6565+let () = Dot.output_graph stdout graph
···9999 in
100100 add g1 (B.copy g2)
101101102102- let replace_by_transitive_reduction ?(reflexive=false) g0 =
103103- (* first compute reachability in g0 using a DFS from each vertex *)
102102+ (* source: tred.c from Graphviz
103103+ time and space O(VE) *)
104104+ let replace_by_transitive_reduction ?(reflexive=false) g =
104105 let module H = Hashtbl.Make(G.V) in
105105- let module D = Traverse.Dfs(G) in
106106- let reachable = H.create (G.nb_vertex g0) in
107107- let path_from v =
108108- let s = H.create 8 in
109109- H.add reachable v s;
110110- D.prefix_component (fun w -> H.add s w ()) g0 v in
111111- G.iter_vertex path_from g0;
112112- let path u v = H.mem (H.find reachable u) v in
113113- (* then remove redundant edges *)
114114- let phi v g =
115115- let g = if reflexive then B.remove_edge g v v else g in
116116- G.fold_succ
117117- (fun sv g ->
118118- G.fold_succ
119119- (fun sv' g ->
120120- if not (G.V.equal sv sv') && path sv sv'
121121- then B.remove_edge g v sv' else g)
122122- g v g)
123123- g v g
106106+ let reduce g v0 =
107107+ (* runs a DFS from v0 and records the length (=1 or >1) of paths from
108108+ v0 for reachable vertices *)
109109+ let nv = G.nb_vertex g in
110110+ let dist = H.create nv in
111111+ G.iter_vertex (fun w -> H.add dist w 0) g;
112112+ let update v w = H.replace dist w (1 + min 1 (H.find dist v)) in
113113+ let onstack = H.create nv in
114114+ let push v st = H.replace onstack v (); (v, G.succ g v) :: st in
115115+ let rec dfs = function
116116+ | [] -> ()
117117+ | (v, []) :: st ->
118118+ H.remove onstack v; dfs st
119119+ | (v, w :: sv) :: st when G.V.equal w v || H.mem onstack w ->
120120+ dfs ((v, sv) :: st)
121121+ | (v, w :: sv) :: st ->
122122+ if H.find dist w = 0 then (
123123+ update v w;
124124+ dfs (push w ((v, sv) :: st))
125125+ ) else (
126126+ if H.find dist w = 1 then update v w;
127127+ dfs ((v, sv) :: st)
128128+ ) in
129129+ dfs (push v0 []);
130130+ (* then delete any edge v0->v when the distance for v is >1 *)
131131+ let delete g v =
132132+ if G.V.equal v v0 && reflexive || H.find dist v > 1
133133+ then B.remove_edge g v0 v else g in
134134+ let sv0 = G.fold_succ (fun v sv0 -> v :: sv0) g v0 [] in
135135+ (* CAVEAT: iterate *then* modify *)
136136+ List.fold_left delete g sv0
124137 in
125125- G.fold_vertex phi g0 g0
138138+ (* run the above from any vertex *)
139139+ let vl = G.fold_vertex (fun v vl -> v :: vl) g [] in
140140+ (* CAVEAT: iterate *then* modify *)
141141+ List.fold_left reduce g vl
126142127143 let transitive_reduction ?(reflexive=false) g0 =
128144 replace_by_transitive_reduction ~reflexive (B.copy g0)
+8-3
src/oper.mli
···3434 (then acts as [transitive_closure]). *)
35353636 val transitive_reduction : ?reflexive:bool -> g -> g
3737- (** [transitive_reduction ?reflexive g] returns the transitive reduction
3838- of [g] (as a new graph). Loops (i.e. edges from a vertex to itself)
3939- are removed only if [reflexive] is [true] (default is [false]). *)
3737+ (** [transitive_reduction ?reflexive g] returns the transitive
3838+ reduction of [g] (as a new graph). This is a subgraph of [g]
3939+ with the same transitive closure as [g]. When [g] is acyclic,
4040+ its transitive reduction contains as few edges as possible and
4141+ is unique.
4242+ Loops (i.e. edges from a vertex to itself) are removed only if
4343+ [reflexive] is [true] (default is [false]).
4444+ Note: Only meaningful for directed graphs. *)
40454146 val replace_by_transitive_reduction : ?reflexive:bool -> g -> g
4247 (** [replace_by_transitive_reduction ?reflexive g] replaces [g] by its
+16-1
tests/check.ml
···756756757757 let check_included g1 g2 =
758758 iter_vertex (fun v -> assert (mem_vertex g2 v)) g1;
759759- iter_edges (fun u v -> assert (mem_edge g1 u v)) g1
759759+ iter_edges (fun u v -> assert (mem_edge g2 u v)) g1
760760761761 let check_same_graph g1 g2 =
762762 check_included g1 g2;
763763 check_included g2 g1
764764765765 let test v e =
766766+ (* Format.eprintf "v=%d e=%d@." v e; *)
766767 let g = R.graph ~loops:true ~v ~e () in
768768+ (* Format.eprintf "g:@."; *)
769769+ (* iter_edges (fun u v -> Format.eprintf " %d->%d@." u v) g; *)
767770 let t = O.transitive_closure g in
768771 check_included g t;
769772 let r = O.transitive_reduction g in
773773+ (* Format.eprintf "r:@."; *)
774774+ (* iter_edges (fun u v -> Format.eprintf " %d->%d@." u v) r; *)
770775 check_included r g;
771776 check_same_graph (O.transitive_closure r) t
772777···785790 add_edge g 2 5;
786791 let r = O.transitive_reduction g in
787792 check_included r g;
793793+ (* iter_edges (fun u v -> Format.eprintf " %d->%d@." u v) r; *)
788794 assert (nb_edges r = 4);
789795 assert (not (mem_edge r 2 5));
790796 ()
797797+798798+ (* issue #145 *)
799799+ let () =
800800+ let g = create () in
801801+ for v = 1 to 3 do add_vertex g v done;
802802+ add_edge g 1 2; add_edge g 2 1;
803803+ add_edge g 3 1; add_edge g 3 2;
804804+ let r = O.transitive_reduction g in
805805+ check_same_graph (O.transitive_closure r) (O.transitive_closure g)
791806792807end
793808