Skip to content

Commit d4aa167

Browse files
authored
Merge branch 'backtracking:master' into master
2 parents 4d0ffbb + 4ad137f commit d4aa167

File tree

10 files changed

+145
-43
lines changed

10 files changed

+145
-43
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11

2+
# 2.1.0 (August 30, 2023)
3+
4+
- :exclamation: OCamlGraph now requires OCaml >= 4.08
5+
- :exclamation: [Traverse]: fixed [Dfs.fold] and [Dfs.fold_component],
6+
which were not implementing a proper DFS
27
- [Classic]: new functions [cycle] and [grid]
38
- [Eulerian]: Eulerian paths (new module)
49
- [Components]: strong articulation points (see functors [Connectivity]

ocamlgraph.opam

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
opam-version: "2.0"
22
synopsis: "A generic graph library for OCaml"
33
description: "Provides both graph data structures and graph algorithms"
4-
maintainer: ["filliatr@lri.fr"]
4+
maintainer: ["jean-christophe.filliatre@cnrs.fr"]
55
authors: ["Sylvain Conchon" "Jean-Christophe Filliâtre" "Julien Signoles"]
66
license: "LGPL-2.1-only"
77
tags: [
@@ -18,13 +18,13 @@ homepage: "https://github.com/backtracking/ocamlgraph/"
1818
doc: "https://backtracking.github.io/ocamlgraph"
1919
bug-reports: "https://github.com/backtracking/ocamlgraph/issues/new"
2020
depends: [
21-
"ocaml" {>= "4.03.0"}
21+
"ocaml" {>= "4.08.0"}
2222
"stdlib-shims"
2323
"dune" {>= "2.0"}
2424
"graphics" {with-test}
2525
]
2626
build: [
27-
["dune" "subst"] {pinned}
27+
["dune" "subst"] {dev}
2828
[
2929
"dune"
3030
"build"

ocamlgraph_gtk.opam

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
opam-version: "2.0"
22
synopsis: "Displaying graphs using OCamlGraph and GTK"
33
description: "Displaying graphs using OCamlGraph and GTK"
4-
maintainer: ["filliatr@lri.fr"]
4+
maintainer: ["jean-christophe.filliatre@cnrs.fr"]
55
authors: ["Sylvain Conchon" "Jean-Christophe Filliâtre" "Julien Signoles"]
66
license: "LGPL-2.1-only"
77
tags: [
@@ -18,7 +18,7 @@ homepage: "https://github.com/backtracking/ocamlgraph/"
1818
doc: "https://backtracking.github.io/ocamlgraph"
1919
bug-reports: "https://github.com/backtracking/ocamlgraph/issues/new"
2020
depends: [
21-
"ocaml" {>= "4.03.0"}
21+
"ocaml" {>= "4.08.0"}
2222
"stdlib-shims"
2323
"lablgtk"
2424
"conf-gnomecanvas"
@@ -27,7 +27,7 @@ depends: [
2727
"graphics" {with-test}
2828
]
2929
build: [
30-
["dune" "subst"] {pinned}
30+
["dune" "subst"] {dev}
3131
[
3232
"dune"
3333
"build"

src/cycles.ml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct
1414

1515
exception Stuck of G.vertex list
1616

17-
module IM = Map.Make (Int)
17+
module IM = Map.Make (struct type t = int let compare = Stdlib.compare end)
1818
module VM = Map.Make (G.V)
1919
module VS = Set.Make (G.V)
2020

@@ -187,6 +187,7 @@ struct
187187
of an obligatory arc. Use the "unbalanced" heuristic impllemented in
188188
[takemax] to discriminate between competing possibilities. If a vertex
189189
is found, remove it from the returned delta bins. *)
190+
(*
190191
let max_from_deltas g ({ delta_bins; _ } as st) =
191192
let rec f = function
192193
| Seq.Nil -> None
@@ -196,6 +197,18 @@ struct
196197
| Some (_, v) -> Some (v, remove_from_bin v st))
197198
in
198199
f (IM.to_rev_seq delta_bins ())
200+
*)
201+
let max_from_deltas g ({ delta_bins; _ } as st) =
202+
let rec f im =
203+
if IM.is_empty im then
204+
None
205+
else
206+
let k, dbin = IM.max_binding im in
207+
(match VS.fold (takemax g) dbin None with
208+
| None -> f (IM.remove k im)
209+
| Some (_, v) -> Some (v, remove_from_bin v st))
210+
in
211+
f delta_bins
199212

200213
(* Include any leftward arcs due to the two-cycles that were removed by
201214
preprocessing. *)

src/traverse.ml

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,23 @@ end
3131
module Dfs(G : G) = struct
3232
module H = Hashtbl.Make(G.V)
3333

34-
let fold f i g =
34+
let fold f acc g =
3535
let h = H.create 97 in
3636
let s = Stack.create () in
37-
let push v =
38-
if not (H.mem h v) then begin H.add h v (); Stack.push v s end
39-
in
4037
let rec loop acc =
4138
if not (Stack.is_empty s) then
4239
let v = Stack.pop s in
43-
let ns = f v acc in
44-
G.iter_succ push g v;
45-
loop ns
40+
if not (H.mem h v) then begin
41+
H.add h v ();
42+
let acc = f v acc in
43+
G.iter_succ (fun w -> Stack.push w s) g v;
44+
loop acc
45+
end else
46+
loop acc
4647
else
4748
acc
4849
in
49-
G.fold_vertex (fun v s -> push v; loop s) g i
50+
G.fold_vertex (fun v acc -> Stack.push v s; loop acc) g acc
5051

5152
let iter ?(pre=fun _ -> ()) ?(post=fun _ -> ()) g =
5253
let h = H.create 97 in
@@ -62,24 +63,24 @@ module Dfs(G : G) = struct
6263

6364
let postfix post g = iter ~post g
6465

65-
let fold_component f i g v0 =
66+
let fold_component f acc g v0 =
6667
let h = H.create 97 in
6768
let s = Stack.create () in
68-
(* invariant: [h] contains exactly the vertices which have been pushed *)
69-
let push v =
70-
if not (H.mem h v) then begin H.add h v (); Stack.push v s end
71-
in
72-
push v0;
69+
Stack.push v0 s;
7370
let rec loop acc =
7471
if not (Stack.is_empty s) then
7572
let v = Stack.pop s in
76-
let ns = f v acc in
77-
G.iter_succ push g v;
78-
loop ns
73+
if not (H.mem h v) then begin
74+
H.add h v ();
75+
let acc = f v acc in
76+
G.iter_succ (fun w -> Stack.push w s) g v;
77+
loop acc
78+
end else
79+
loop acc
7980
else
8081
acc
8182
in
82-
loop i
83+
loop acc
8384

8485
let iter_component ?(pre=fun _ -> ()) ?(post=fun _ -> ()) g v =
8586
let h = H.create 97 in

tests/dune

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
(libraries graph)
44
(modules check))
55

6+
(test
7+
(name test_dfs)
8+
(libraries graph)
9+
(modules test_dfs))
10+
611
(test
712
(name test_topsort)
813
(libraries graph)

tests/test_components.expected

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
7 components
1+
4 components
22
0 -> 0
33
1 -> 1
4-
2 -> 2
5-
3 -> 3
6-
4 -> 1
7-
5 -> 1
8-
6 -> 4
9-
7 -> 1
10-
8 -> 5
11-
9 -> 6
4+
2 -> 0
5+
3 -> 2
6+
4 -> 2
7+
5 -> 3
8+
6 -> 2
9+
7 -> 0

tests/test_components.ml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,20 @@ module C = Components.Undirected(Pack.Graph)
2222

2323
open Pack.Graph
2424

25+
(* 0 -- 2 -- 7 1 3 -- 4 5
26+
\ /
27+
6
28+
29+
component: 0 1 2 3
30+
*)
31+
2532
let () =
26-
Random.init 42;
27-
let g = Rand.graph ~v:10 ~e:3 () in
33+
let g = create () in
34+
let v = Array.init 8 V.create in
35+
Array.iter (add_vertex g) v;
36+
let add i j = add_edge g v.(i) v.(j) in
37+
add 0 2; add 7 2; add 3 4; add 4 6; add 3 6;
2838
let n, f = C.components g in
2939
printf "%d components@." n;
3040
iter_vertex (fun v -> printf "%d -> %d@." (V.label v) (f v)) g
3141

32-
33-
(*
34-
Local Variables:
35-
compile-command: "ocaml -I .. graph.cma test_components.ml"
36-
End:
37-
*)

tests/test_dfs.ml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
2+
(* Stack-based DFS is tricky to get right. See
3+
https://11011110.github.io/blog/2013/12/17/stack-based-graph-traversal.html
4+
5+
On this graph,
6+
7+
0
8+
/ \
9+
/ \
10+
v v
11+
1---2---3 (All edges are undirected,
12+
|\ /| apart from 0->1 0->3 1->5 and 3->6.)
13+
| \ / |
14+
| 4 |
15+
| / \ |
16+
v / \ v
17+
5 6
18+
19+
an incorrect stack-based DFS starting from 0 would first mark 1 and 3,
20+
and then would not go as deep as possible in the traversal.
21+
22+
In the following, we check that, whenever 2 and 4 are visited,
23+
then necessarily both 1 and 3 are already visited.
24+
*)
25+
26+
open Format
27+
open Graph
28+
open Pack.Digraph
29+
30+
let debug = false
31+
32+
let g = create ()
33+
let v = Array.init 7 V.create
34+
let () = Array.iter (add_vertex g) v
35+
let adde x y = add_edge g v.(x) v.(y)
36+
let addu x y = adde x y; adde y x
37+
let () = adde 0 1; adde 0 3
38+
let () = addu 1 2; addu 2 3
39+
let () = adde 1 5; adde 3 6
40+
let () = addu 1 4; addu 4 3; addu 5 4; addu 4 6
41+
42+
let () = assert (Dfs.has_cycle g)
43+
44+
let marked = Array.make 7 false
45+
let reset () = Array.fill marked 0 7 false
46+
let mark v =
47+
let i = V.label v in
48+
marked.(i) <- true;
49+
if marked.(2) && marked.(4) then assert (marked.(1) && marked.(3))
50+
51+
let pre v = if debug then printf "pre %d@." (V.label v); mark v
52+
let post v = if debug then printf "post %d@." (V.label v)
53+
let f v () = if debug then printf "fold %d@." (V.label v); mark v
54+
55+
let () = reset (); Dfs.iter ~pre ~post g
56+
let () = reset (); Dfs.prefix pre g
57+
let () = reset (); Dfs.postfix post g
58+
let () = reset (); Dfs.iter_component ~pre ~post g v.(0)
59+
let () = reset (); Dfs.prefix_component pre g v.(0)
60+
let () = reset (); Dfs.postfix_component post g v.(0)
61+
let () = reset (); Dfs.fold f () g
62+
let () = reset (); Dfs.fold_component f () g v.(0)
63+
64+
module D = Traverse.Dfs(Pack.Digraph)
65+
66+
let rec visit it =
67+
let v = D.get it in
68+
mark v;
69+
visit (D.step it)
70+
71+
let () = try visit (D.start g) with Exit -> ()
72+
73+
let () = printf "All tests succeeded.@."

tests/test_map_vertex.ml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ end
2828
module TestI(G: Sig.I with type V.label = int) = TestB(Builder.I(G))
2929
module TestP(G: Sig.P with type V.label = int) = TestB(Builder.P(G))
3030

31-
module Int = struct include Int let hash x = x let default = 42 end
31+
module Int = struct
32+
type t = int let compare = Stdlib.compare let equal = (=)
33+
let hash x = x let default = 42
34+
end
3235

3336
include TestI(Pack.Digraph)
3437
include TestI(Pack.Graph)

0 commit comments

Comments
 (0)