Skip to content

Commit eabeeed

Browse files
committed
Modify docs and add minor changes
1 parent 6be69c0 commit eabeeed

File tree

10 files changed

+213
-90
lines changed

10 files changed

+213
-90
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ edition = "2021"
1818
license = "MIT"
1919
authors = ["Jonas Spinner"]
2020
repository = "https://github.com/jonasspinner/modular-decomposition"
21+
homepage = "https://github.com/jonasspinner/modular-decomposition"
2122

2223
[workspace.dependencies]
2324
clap = { version = "4.4.7", features = ["derive"] }

README.md

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,36 @@
22

33
This project implements several algorithm to compute the [modular decomposition](https://en.wikipedia.org/wiki/Modular_decomposition) of a graph.
44

5-
+ `fracture`
6-
+ A $O(n + m \log n)$ algorithm based on [[HPV99]](https://doi.org/10.1142/S0129054199000125) and [[CHM02]](https://doi.org/10.46298/dmtcs.298).
7-
+ First sketch based on a Julia implementation [[Kar19]](https://github.com/StefanKarpinski/GraphModularDecomposition.jl), but now heavily modified.
8-
+ `skeleton`
9-
+ A $O(n + m \log n)$ algorithm based on [[MS00]](https://doi.org/10.46298/dmtcs.274).
10-
+ `linear (ref)`
11-
+ A $O(n + m)$ algorithm based on [[TCHP08]](https://doi.org/10.1007/978-3-540-70575-8_52).
12-
+ A wrapper for the C++ implementation of [[Miz23]](https://github.com/mogproject/modular-decomposition).
13-
+ `linear`
14-
+ A $O(n + m)$ algorithm based on [[TCHP08]](https://doi.org/10.1007/978-3-540-70575-8_52).
15-
+ A port of the C++ implementation of [[Miz23]](https://github.com/mogproject/modular-decomposition) to Rust.
16-
+ Some additional modifications.
5+
The project is structures as follows.
6+
7+
**Library**
8+
+ `crates/modular-decomposition` A package providing the `fracture` algorithm based on [[HPV99]](https://doi.org/10.1142/S0129054199000125) and [[CHM02]](https://doi.org/10.46298/dmtcs.298) as a library.
9+
10+
**Algorithms**
11+
+ `crates/linear-ref-sys`/`crates/linear-ref` Bindings and wrapper to reference C++ implementation [[Miz23]](https://github.com/mogproject/modular-decomposition) based on [[TCHP08]](https://doi.org/10.1007/978-3-540-70575-8_52).
12+
+ `crates/linear` Rust port of [[Miz23]](https://github.com/mogproject/modular-decomposition) based on [[TCHP08]](https://doi.org/10.1007/978-3-540-70575-8_52).
13+
+ `crates/skeleton` Based on [[MS00]](https://doi.org/10.46298/dmtcs.274).
14+
+ `crates/fracture` Based on [[HPV99]](https://doi.org/10.1142/S0129054199000125) and [[CHM02]](https://doi.org/10.46298/dmtcs.298). Uses the implementation in `crates/modular-decomposition`.
15+
16+
**Others**
17+
+ `crates/common` Common utilities shared by the algorithm crate
18+
+ `crates/playground` A crate to experiment with related algorithms and data structures.
19+
+ `crates/evaluation` Evaluation of the algorithms on real-world and generated data.
20+
21+
## Usage
22+
23+
The crate `crates/modular-decomposition` provides an API to compute the modular decomposition.
24+
25+
```rust
26+
use petgraph::graph::UnGraph;
27+
use modular_decomposition::{ModuleKind, modular_decomposition};
28+
29+
let graph = UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
30+
let md = modular_decomposition(&graph).unwrap();
31+
32+
assert_eq!(md.module_kind(md.root()), Some(&ModuleKind::Prime));
33+
```
34+
1735

1836
## Evaluation
1937

@@ -22,16 +40,7 @@ This project implements several algorithm to compute the [modular decomposition]
2240
This figure shows the algorithm performance for some datasets.
2341
The time for multiple runs is averaged for each instance and algorithm. The time for each algorithm is divided by the best time and the distribution is plotted.
2442
The `fracture` algorithm performs best for most instances.
25-
26-
## As a library
27-
28-
The crates implementing the algorithms provide a API for the modular decompostion.
29-
```
30-
pub fn modular_decomposition<N, E>(graph: &Graph<N, E, Undirected>) -> DiGraph<MDNodeKind, ()>
31-
{
32-
prepare(graph).compute().finalize()
33-
}
34-
```
43+
The evaluation code can be found in `crates/evaluation`.
3544

3645
## References
3746

crates/fracture/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl Computed {
4545
ModuleKind::Prime => MDNodeKind::Prime,
4646
ModuleKind::Series => MDNodeKind::Series,
4747
ModuleKind::Parallel => MDNodeKind::Parallel,
48-
ModuleKind::Vertex(v) => MDNodeKind::Vertex(v.index()),
48+
ModuleKind::Node(v) => MDNodeKind::Vertex(v.index()),
4949
},
5050
|_, _| (),
5151
)

crates/modular-decomposition/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
[package]
22
name = "modular-decomposition"
33
version.workspace = true
4-
authors.workspace = true
54
edition.workspace = true
65
license.workspace = true
6+
authors.workspace = true
77
repository.workspace = true
8+
homepage.workspace = true
89
readme = "README.md"
10+
description = "A library for computing the modular decomposition of a graph"
911
categories = ["science", "algorithms"]
1012
keywords = ["graph", "modular", "decomposition"]
1113

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Jonas Spinner
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,55 @@
11
# Modular Decomposition
22

3-
A $O(n + m \log n)$ algorithm based on [[HPV99]](https://doi.org/10.1142/S0129054199000125) and [[CHM02]](https://doi.org/10.46298/dmtcs.298).
3+
This is a library to compute the [modular decomposition](https://en.wikipedia.org/wiki/Modular_decomposition) of a
4+
simple, undirected graph.
45

5-
## Evaluation
6+
A node set *M* is a *module* if every node has the same neighborhood outside *M*. The set of all nodes *V* and the sets
7+
with a single node *{u}* are
8+
trivial modules.
69

7-
![](../../evaluation.png)
10+
The modular decomposition algorithm in this library has a O(n + m log n) running time and is based
11+
on [[HPV99]](https://doi.org/10.1142/S0129054199000125) and [[CHM02]](https://doi.org/10.46298/dmtcs.298). Although
12+
linear time algorithms exists, they perform worse in comparison.
13+
14+
## Examples
15+
16+
The smallest prime graph is the path graph on 4 nodes.
17+
18+
```rust
19+
use petgraph::graph::UnGraph;
20+
use modular_decomposition::{ModuleKind, modular_decomposition};
21+
22+
// a path graph with 4 nodes
23+
let graph = UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 3)]);
24+
let md = modular_decomposition(&graph).unwrap();
25+
26+
assert_eq!(md.module_kind(md.root()), Some(&ModuleKind::Prime));
27+
```
28+
29+
Determining whether a graph is a [cograph](https://en.wikipedia.org/wiki/Cograph).
830

9-
This figure shows the algorithm performance for some datasets.
10-
The time for multiple runs is averaged for each instance and algorithm. The time for each algorithm is divided by the best time and the distribution is plotted.
11-
The `fracture` algorithm performs best for most instances.
31+
```rust
32+
use petgraph::graph::UnGraph;
33+
use modular_decomposition::{ModuleKind, modular_decomposition};
1234

13-
## References
35+
// a complete graph with 3 nodes
36+
let graph = UnGraph::<(), ()>::from_edges([(0, 1), (0, 2), (1, 2)]);
37+
let md = modular_decomposition(&graph).unwrap();
38+
39+
// a graph is a cograph exactly if none of its modules is prime
40+
let is_cograph = md.module_kinds().all(|kind| *kind != ModuleKind::Prime);
41+
assert!(is_cograph);
42+
```
43+
44+
## Generics
45+
46+
The algorithm is implemented for structs that implement the `petgraph`
47+
traits `NodeCompactIndexable`, `IntoNeighbors`, and `GraphProp<EdgeType = Undirected>`.
48+
49+
## Evaluation
50+
51+
![](../../evaluation.png)
1452

15-
+ [HPV99] Michel Habib, Christophe Paul, and Laurent Viennot. “Partition Refinement Techniques: An Interesting Algorithmic Tool Kit”. https://doi.org/10.1142/S0129054199000125.
16-
+ [CHM02] Christian Capelle, Michel Habib, and Fabien Montgolfier. “Graph Decompositions and Factorizing Permutations”. https://doi.org/10.46298/dmtcs.298
17-
+ [MS00] Ross M. Mcconnell and Jeremy P. Spinrad. “Ordered Vertex Partitioning”. https://doi.org/10.46298/dmtcs.274
18-
+ [TCHP08] Marc Tedder, Derek Corneil, Michel Habib, and Christophe Paul. “Simpler Linear-Time Modular Decomposition Via Recursive Factorizing Permutations”. https://doi.org/10.1007/978-3-540-70575-8_52.
19-
+ [Miz23] https://github.com/mogproject/modular-decomposition
53+
As part of a thesis, we evaluated four implementations of modular decomposition algorithms.
54+
The `fracture` algorithm performs best and is provided in this library. For more information see
55+
the [repository](https://github.com/jonasspinner/modular-decomposition/).

crates/modular-decomposition/examples/adj_vector_graph.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use modular_decomposition::modular_decomposition;
2-
use petgraph::visit::{GraphBase, GraphProp, IntoNeighbors, NodeCompactIndexable, NodeCount, NodeIndexable};
3-
use petgraph::Undirected;
1+
use modular_decomposition::{modular_decomposition, ModuleKind};
2+
use petgraph::dot::Config::EdgeNoLabel;
3+
use petgraph::dot::Dot;
4+
use petgraph::visit::{Dfs, GraphBase, GraphProp, IntoNeighbors, NodeCompactIndexable, NodeCount, NodeIndexable};
5+
use petgraph::{Incoming, Undirected};
46

57
struct Graph(Vec<Vec<usize>>);
68

@@ -69,5 +71,17 @@ impl NodeCompactIndexable for Graph {}
6971

7072
fn main() {
7173
let graph = Graph::from_edges([(0, 1), (1, 2), (2, 3)]);
72-
let _tree = modular_decomposition(&graph);
74+
let tree = modular_decomposition(&graph).map(|tree| tree.into_digraph()).unwrap_or_default();
75+
println!("{:?}", Dot::with_config(&tree, &[EdgeNoLabel]));
76+
77+
let mut factorizing_permutation = Vec::new();
78+
let root = tree.externals(Incoming).next().unwrap();
79+
let mut dfs = Dfs::new(&tree, root);
80+
while let Some(node) = dfs.next(&tree) {
81+
if let ModuleKind::Node(u) = tree[node] {
82+
factorizing_permutation.push(u);
83+
}
84+
}
85+
let factorizing_permutation: Vec<_> = factorizing_permutation.iter().map(|u| u.index()).collect();
86+
println!("{:?}", factorizing_permutation);
7387
}

crates/modular-decomposition/src/fracture/mod.rs

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
mod factorizing_permutation;
22

33
use crate::fracture::factorizing_permutation::{factorizing_permutation, Permutation};
4-
use crate::md_tree::{MDTree, ModuleKind, NodeIndex, NullGraph};
4+
use crate::md_tree::{MDTree, ModuleKind, NodeIndex, NullGraphError};
55
use crate::segmented_stack::SegmentedStack;
66
use petgraph::graph::DiGraph;
77
use petgraph::visit::{GraphProp, IntoNeighbors, NodeCompactIndexable};
88
use petgraph::Undirected;
99
use tracing::{info, instrument};
1010

11-
/// Computes the modular decomposition.
11+
/// Computes the modular decomposition of the graph.
12+
///
13+
/// # Errors
14+
///
15+
/// Returns a `NullGraphError` if the input graph does not contain any nodes or edges.
1216
#[instrument(skip_all)]
13-
pub fn modular_decomposition<G>(graph: G) -> Result<MDTree, NullGraph>
17+
pub fn modular_decomposition<G>(graph: G) -> Result<MDTree, NullGraphError>
1418
where
1519
G: NodeCompactIndexable + IntoNeighbors + GraphProp<EdgeType = Undirected>,
1620
{
1721
let n = graph.node_bound();
1822
if n == 0 {
19-
return Err(NullGraph);
23+
return Err(NullGraphError);
2024
}
2125
if n == 1 {
2226
let mut tree = DiGraph::new();
23-
tree.add_node(ModuleKind::Vertex(NodeIndex::new(0)));
27+
tree.add_node(ModuleKind::Node(NodeIndex::new(0)));
2428
return MDTree::from_digraph(tree);
2529
}
2630

@@ -141,7 +145,7 @@ pub(crate) fn build_parenthesizing<G>(
141145

142146
#[instrument(skip_all)]
143147
pub(crate) fn remove_non_module_dummy_nodes(op: &mut [u32], cl: &mut [u32], lc: &[u32], uc: &[u32]) {
144-
#[derive(Clone)]
148+
#[derive(Clone, Debug)]
145149
struct Info {
146150
first_vertex: u32,
147151
last_vertex: u32,
@@ -216,36 +220,40 @@ pub(crate) fn remove_non_module_dummy_nodes(op: &mut [u32], cl: &mut [u32], lc:
216220

217221
#[instrument(skip_all)]
218222
pub(crate) fn create_consecutive_twin_nodes(op: &mut [u32], cl: &mut [u32], lc: &[u32], uc: &[u32]) {
219-
let n = op.len();
220-
let mut s = Vec::with_capacity(n);
221-
let mut l = 0;
222-
for k in 0..n {
223-
s.push((k, l));
224-
l = k;
225-
s.extend(std::iter::repeat((k, k)).take(op[k] as _));
226-
for c in (0..cl[k] + 1).rev() {
227-
let (j, i) = s.pop().unwrap();
228-
229-
l = i;
230-
if i >= j {
231-
continue;
223+
let handle_inner_node = |op: &mut [u32], cl: &mut [u32], nodes: &[(u32, u32)]| -> (u32, u32) {
224+
if nodes.len() == 1 {
225+
return nodes[0];
226+
}
227+
let mut add_brackets = |start: &mut Option<u32>, end: u32| {
228+
if let Some(start) = start.take() {
229+
op[start as usize] += 1;
230+
cl[end as usize] += 1;
232231
}
233-
let (a, b) = (lc[j - 1] as usize, uc[j - 1] as usize);
234-
if i <= a && a < b && b <= k {
235-
if c > 0 {
236-
op[i] += 1;
237-
cl[k] += 1;
238-
l = k + 1;
239-
}
232+
};
233+
let mut start = None;
234+
for (&(first_vertex, end), &(_, last_vertex)) in nodes.iter().zip(nodes.iter().skip(1)) {
235+
if first_vertex <= lc[end as usize] && uc[end as usize] <= last_vertex {
236+
start = start.or(Some(first_vertex));
240237
} else {
241-
if i < j - 1 {
242-
op[i] += 1;
243-
cl[j - 1] += 1;
244-
}
245-
l = j;
238+
add_brackets(&mut start, end)
246239
}
247240
}
241+
let ((first_vertex, _), (_, last_vertex)) = (nodes[0], nodes[nodes.len() - 1]);
242+
add_brackets(&mut start, last_vertex);
243+
(first_vertex, last_vertex)
244+
};
245+
246+
let mut s = SegmentedStack::with_capacity(op.len());
247+
for j in 0..op.len() {
248+
s.extend(op[j] as _);
249+
s.add((j as u32, j as u32));
250+
for _ in 0..cl[j] {
251+
let info = handle_inner_node(op, cl, s.pop());
252+
s.add(info);
253+
}
248254
}
255+
assert!(s.is_empty());
256+
debug_assert_eq!(op.iter().sum::<u32>(), cl.iter().sum());
249257
}
250258

251259
#[instrument(skip_all)]
@@ -263,7 +271,7 @@ where
263271

264272
let handle_vertex_node = |t: &mut DiGraph<ModuleKind, ()>,
265273
x: NodeIndex|
266-
-> (NodeIndex, petgraph::graph::NodeIndex) { (x, t.add_node(ModuleKind::Vertex(x))) };
274+
-> (NodeIndex, petgraph::graph::NodeIndex) { (x, t.add_node(ModuleKind::Node(x))) };
267275

268276
let mut marked = vec![0; n];
269277
let mut gen = 0;
@@ -334,6 +342,7 @@ where
334342
if nodes.len() == 1 {
335343
return nodes[0];
336344
}
345+
assert!(nodes.len() > 1);
337346
let (y, kind) = determine_node_kind(t, nodes);
338347
let idx = t.add_node(kind);
339348
for (_, u) in nodes {
@@ -370,7 +379,7 @@ mod test {
370379
remove_non_module_dummy_nodes,
371380
};
372381
use petgraph::dot::{Config, Dot};
373-
use petgraph::graph::{DiGraph, UnGraph};
382+
use petgraph::graph::UnGraph;
374383
use petgraph::visit::NodeIndexable;
375384

376385
fn print_parenthesis(op: &[u32], cl: &[u32], permutation: &Permutation) {

0 commit comments

Comments
 (0)