Skip to content

Commit c40b169

Browse files
authored
Add Dorogovtsev-Goltsev-Mendes graph generator (#1206)
* Add DGM graph generator to rustworkx-core * Add DGM graph generator to Python library * Change parameter t to n (state to transitions) * Add rust tests and docs * Add stubs and release notes * Improve python tests
1 parent 8af19f0 commit c40b169

File tree

7 files changed

+212
-0
lines changed

7 files changed

+212
-0
lines changed

docs/source/api/generators.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ Generators
3232
rustworkx.generators.directed_empty_graph
3333
rustworkx.generators.complete_graph
3434
rustworkx.generators.directed_complete_graph
35+
rustworkx.generators.dorogovtsev_goltsev_mendes_graph
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
Added :func:`rustworkx.generators.dorogovtsev_goltsev_mendes_graph` that generates
5+
deterministic scale-free graphs using the Dorogovtsev-Goltsev-Mendes iterative procedure.
6+
- |
7+
Added to rustworkx-for ``generators::dorogovtsev_goltsev_mendes_graph`` function that generates,
8+
deterministic scale-free graphs using the Dorogovtsev-Goltsev-Mendes iterative procedure.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
// not use this file except in compliance with the License. You may obtain
3+
// a copy of the License at
4+
//
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
//
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
// License for the specific language governing permissions and limitations
11+
// under the License.
12+
13+
use petgraph::{data::Create, visit::Data};
14+
15+
use super::InvalidInputError;
16+
17+
/// Generate a Dorogovtsev-Goltsev-Mendes graph
18+
///
19+
/// Generate a graph following the recursive procedure in [1].
20+
/// Starting from the two-node, one-edge graph, iterating `n` times generates
21+
/// a graph with `(3**n + 3) // 2` nodes and `3**n` edges.
22+
///
23+
///
24+
/// Arguments:
25+
///
26+
/// * `n` - The number of iterations to perform. n=0 returns the two-node, one-edge graph.
27+
/// * `default_node_weight` - A callable that will return the weight to use for newly created nodes.
28+
/// * `default_edge_weight` - A callable that will return the weight object to use for newly created edges.
29+
///
30+
/// # Example
31+
/// ```rust
32+
/// use rustworkx_core::petgraph;
33+
/// use rustworkx_core::generators::dorogovtsev_goltsev_mendes_graph;
34+
/// use rustworkx_core::petgraph::visit::EdgeRef;
35+
///
36+
/// let g: petgraph::graph::UnGraph<(), ()> = dorogovtsev_goltsev_mendes_graph(2, || (), || ()).unwrap();
37+
/// assert_eq!(g.node_count(), 6);
38+
/// assert_eq!(
39+
/// vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3), (0, 4), (2, 4), (1, 5), (2, 5)],
40+
/// g.edge_references()
41+
/// .map(|edge| (edge.source().index(), edge.target().index()))
42+
/// .collect::<Vec<(usize, usize)>>(),
43+
/// );
44+
/// ```
45+
///
46+
/// .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes
47+
/// “Pseudofractal scale-free web”
48+
/// Physical Review E 65, 066122, 2002
49+
/// https://arxiv.org/abs/cond-mat/0112143
50+
///
51+
pub fn dorogovtsev_goltsev_mendes_graph<G, T, F, H, M>(
52+
n: usize,
53+
mut default_node_weight: F,
54+
mut default_edge_weight: H,
55+
) -> Result<G, InvalidInputError>
56+
where
57+
G: Create + Data<NodeWeight = T, EdgeWeight = M>,
58+
F: FnMut() -> T,
59+
H: FnMut() -> M,
60+
{
61+
let n_edges = usize::pow(3, n as u32);
62+
let n_nodes = (n_edges + 3) / 2;
63+
let mut graph = G::with_capacity(n_nodes, n_edges);
64+
65+
let node_0 = graph.add_node(default_node_weight());
66+
let node_1 = graph.add_node(default_node_weight());
67+
graph
68+
.add_edge(node_0, node_1, default_edge_weight())
69+
.unwrap();
70+
let mut current_endpoints = vec![(node_0, node_1)];
71+
72+
for _ in 0..n {
73+
let mut new_endpoints = vec![];
74+
for (source, target) in current_endpoints.iter() {
75+
let new_node = graph.add_node(default_node_weight());
76+
graph.add_edge(*source, new_node, default_edge_weight());
77+
new_endpoints.push((*source, new_node));
78+
graph.add_edge(*target, new_node, default_edge_weight());
79+
new_endpoints.push((*target, new_node));
80+
}
81+
current_endpoints.extend(new_endpoints);
82+
}
83+
Ok(graph)
84+
}
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use crate::generators::dorogovtsev_goltsev_mendes_graph;
89+
use crate::petgraph::graph::Graph;
90+
use crate::petgraph::visit::EdgeRef;
91+
92+
#[test]
93+
fn test_dorogovtsev_goltsev_mendes_graph() {
94+
for n in 0..6 {
95+
let graph: Graph<(), ()> = match dorogovtsev_goltsev_mendes_graph(n, || (), || ()) {
96+
Ok(graph) => graph,
97+
Err(_) => panic!("Error generating graph"),
98+
};
99+
assert_eq!(graph.node_count(), (usize::pow(3, n as u32) + 3) / 2);
100+
assert_eq!(graph.edge_count(), usize::pow(3, n as u32));
101+
}
102+
}
103+
104+
#[test]
105+
fn test_dorogovtsev_goltsev_mendes_graph_edges() {
106+
let n = 2;
107+
let expected_edge_list = vec![
108+
(0, 1),
109+
(0, 2),
110+
(1, 2),
111+
(0, 3),
112+
(1, 3),
113+
(0, 4),
114+
(2, 4),
115+
(1, 5),
116+
(2, 5),
117+
];
118+
let graph: Graph<(), ()> = match dorogovtsev_goltsev_mendes_graph(n, || (), || ()) {
119+
Ok(graph) => graph,
120+
Err(_) => panic!("Error generating graph"),
121+
};
122+
assert_eq!(
123+
expected_edge_list,
124+
graph
125+
.edge_references()
126+
.map(|edge| (edge.source().index(), edge.target().index()))
127+
.collect::<Vec<(usize, usize)>>(),
128+
)
129+
}
130+
}

rustworkx-core/src/generators/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod barbell_graph;
1616
mod binomial_tree_graph;
1717
mod complete_graph;
1818
mod cycle_graph;
19+
mod dorogovtsev_goltsev_mendes_graph;
1920
mod full_rary_tree_graph;
2021
mod grid_graph;
2122
mod heavy_hex_graph;
@@ -48,6 +49,7 @@ pub use barbell_graph::barbell_graph;
4849
pub use binomial_tree_graph::binomial_tree_graph;
4950
pub use complete_graph::complete_graph;
5051
pub use cycle_graph::cycle_graph;
52+
pub use dorogovtsev_goltsev_mendes_graph::dorogovtsev_goltsev_mendes_graph;
5153
pub use full_rary_tree_graph::full_rary_tree_graph;
5254
pub use grid_graph::grid_graph;
5355
pub use heavy_hex_graph::heavy_hex_graph;

rustworkx/generators/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,4 @@ def directed_complete_graph(
126126
weights: Sequence[Any] | None = ...,
127127
multigraph: bool = ...,
128128
) -> PyDiGraph: ...
129+
def dorogovtsev_goltsev_mendes_graph(n: int) -> PyGraph: ...

src/generators.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,47 @@ pub fn directed_complete_graph(
16181618
})
16191619
}
16201620

1621+
/// Generate a Dorogovtsev-Goltsev-Mendes graph.
1622+
///
1623+
/// Generate a graph following the recursive procedure in [1]_ .
1624+
/// Starting from the two-node, one-edge graph, iterating `n` times generates
1625+
/// a graph with `(3**n + 3) // 2` nodes and `3**n` edges.
1626+
///
1627+
/// :param int n: The number of iterations to perform.
1628+
///
1629+
/// :returns: The generated Dorogovtsev-Goltsev-Mendes graph
1630+
///
1631+
/// :rtype: PyGraph
1632+
///
1633+
/// .. jupyter-execute::
1634+
///
1635+
/// import rustworkx.generators
1636+
/// from rustworkx.visualization import mpl_draw
1637+
///
1638+
/// graph = rustworkx.generators.dorogovtsev_goltsev_mendes_graph(2)
1639+
/// mpl_draw(graph)
1640+
///
1641+
/// .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes
1642+
/// "Pseudofractal scale-free web"
1643+
/// Physical Review E 65, 066122, 2002
1644+
/// https://arxiv.org/abs/cond-mat/0112143
1645+
///
1646+
#[pyfunction]
1647+
#[pyo3(signature=(n,))]
1648+
pub fn dorogovtsev_goltsev_mendes_graph(py: Python, n: usize) -> PyResult<graph::PyGraph> {
1649+
let default_fn = || py.None();
1650+
let graph = match core_generators::dorogovtsev_goltsev_mendes_graph(n, default_fn, default_fn) {
1651+
Ok(graph) => graph,
1652+
Err(_) => return Err(PyIndexError::new_err("t must be >= -1")),
1653+
};
1654+
Ok(graph::PyGraph {
1655+
graph,
1656+
node_removed: false,
1657+
multigraph: false,
1658+
attrs: py.None(),
1659+
})
1660+
}
1661+
16211662
#[pymodule]
16221663
pub fn generators(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
16231664
m.add_wrapped(wrap_pyfunction!(cycle_graph))?;
@@ -1646,5 +1687,6 @@ pub fn generators(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
16461687
m.add_wrapped(wrap_pyfunction!(directed_empty_graph))?;
16471688
m.add_wrapped(wrap_pyfunction!(complete_graph))?;
16481689
m.add_wrapped(wrap_pyfunction!(directed_complete_graph))?;
1690+
m.add_wrapped(wrap_pyfunction!(dorogovtsev_goltsev_mendes_graph))?;
16491691
Ok(())
16501692
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
import unittest
14+
15+
import rustworkx
16+
17+
18+
class TestDorogovtsevGoltsevMendesGraph(unittest.TestCase):
19+
def test_dorogovtsev_goltsev_mendes_graph(self):
20+
for n in range(0, 6):
21+
with self.subTest(n=n):
22+
graph = rustworkx.generators.dorogovtsev_goltsev_mendes_graph(n)
23+
self.assertEqual(len(graph), (3**n + 3) // 2)
24+
self.assertEqual(len(graph.edges()), 3**n)
25+
self.assertTrue(rustworkx.is_planar(graph))
26+
27+
def test_dorogovstev_goltsev_mendes_graph_error(self):
28+
self.assertRaises(OverflowError, rustworkx.generators.dorogovtsev_goltsev_mendes_graph, -1)

0 commit comments

Comments
 (0)