Skip to content

Commit

Permalink
Add Dorogovtsev-Goltsev-Mendes graph generator (#1206)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dfacoet authored Jun 1, 2024
1 parent 8af19f0 commit c40b169
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/source/api/generators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ Generators
rustworkx.generators.directed_empty_graph
rustworkx.generators.complete_graph
rustworkx.generators.directed_complete_graph
rustworkx.generators.dorogovtsev_goltsev_mendes_graph
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
Added :func:`rustworkx.generators.dorogovtsev_goltsev_mendes_graph` that generates
deterministic scale-free graphs using the Dorogovtsev-Goltsev-Mendes iterative procedure.
- |
Added to rustworkx-for ``generators::dorogovtsev_goltsev_mendes_graph`` function that generates,
deterministic scale-free graphs using the Dorogovtsev-Goltsev-Mendes iterative procedure.
130 changes: 130 additions & 0 deletions rustworkx-core/src/generators/dorogovtsev_goltsev_mendes_graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

use petgraph::{data::Create, visit::Data};

use super::InvalidInputError;

/// Generate a Dorogovtsev-Goltsev-Mendes graph
///
/// Generate a graph following the recursive procedure in [1].
/// Starting from the two-node, one-edge graph, iterating `n` times generates
/// a graph with `(3**n + 3) // 2` nodes and `3**n` edges.
///
///
/// Arguments:
///
/// * `n` - The number of iterations to perform. n=0 returns the two-node, one-edge graph.
/// * `default_node_weight` - A callable that will return the weight to use for newly created nodes.
/// * `default_edge_weight` - A callable that will return the weight object to use for newly created edges.
///
/// # Example
/// ```rust
/// use rustworkx_core::petgraph;
/// use rustworkx_core::generators::dorogovtsev_goltsev_mendes_graph;
/// use rustworkx_core::petgraph::visit::EdgeRef;
///
/// let g: petgraph::graph::UnGraph<(), ()> = dorogovtsev_goltsev_mendes_graph(2, || (), || ()).unwrap();
/// assert_eq!(g.node_count(), 6);
/// assert_eq!(
/// vec![(0, 1), (0, 2), (1, 2), (0, 3), (1, 3), (0, 4), (2, 4), (1, 5), (2, 5)],
/// g.edge_references()
/// .map(|edge| (edge.source().index(), edge.target().index()))
/// .collect::<Vec<(usize, usize)>>(),
/// );
/// ```
///
/// .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes
/// “Pseudofractal scale-free web”
/// Physical Review E 65, 066122, 2002
/// https://arxiv.org/abs/cond-mat/0112143
///
pub fn dorogovtsev_goltsev_mendes_graph<G, T, F, H, M>(
n: usize,
mut default_node_weight: F,
mut default_edge_weight: H,
) -> Result<G, InvalidInputError>
where
G: Create + Data<NodeWeight = T, EdgeWeight = M>,
F: FnMut() -> T,
H: FnMut() -> M,
{
let n_edges = usize::pow(3, n as u32);
let n_nodes = (n_edges + 3) / 2;
let mut graph = G::with_capacity(n_nodes, n_edges);

let node_0 = graph.add_node(default_node_weight());
let node_1 = graph.add_node(default_node_weight());
graph
.add_edge(node_0, node_1, default_edge_weight())
.unwrap();
let mut current_endpoints = vec![(node_0, node_1)];

for _ in 0..n {
let mut new_endpoints = vec![];
for (source, target) in current_endpoints.iter() {
let new_node = graph.add_node(default_node_weight());
graph.add_edge(*source, new_node, default_edge_weight());
new_endpoints.push((*source, new_node));
graph.add_edge(*target, new_node, default_edge_weight());
new_endpoints.push((*target, new_node));
}
current_endpoints.extend(new_endpoints);
}
Ok(graph)
}

#[cfg(test)]
mod tests {
use crate::generators::dorogovtsev_goltsev_mendes_graph;
use crate::petgraph::graph::Graph;
use crate::petgraph::visit::EdgeRef;

#[test]
fn test_dorogovtsev_goltsev_mendes_graph() {
for n in 0..6 {
let graph: Graph<(), ()> = match dorogovtsev_goltsev_mendes_graph(n, || (), || ()) {
Ok(graph) => graph,
Err(_) => panic!("Error generating graph"),
};
assert_eq!(graph.node_count(), (usize::pow(3, n as u32) + 3) / 2);
assert_eq!(graph.edge_count(), usize::pow(3, n as u32));
}
}

#[test]
fn test_dorogovtsev_goltsev_mendes_graph_edges() {
let n = 2;
let expected_edge_list = vec![
(0, 1),
(0, 2),
(1, 2),
(0, 3),
(1, 3),
(0, 4),
(2, 4),
(1, 5),
(2, 5),
];
let graph: Graph<(), ()> = match dorogovtsev_goltsev_mendes_graph(n, || (), || ()) {
Ok(graph) => graph,
Err(_) => panic!("Error generating graph"),
};
assert_eq!(
expected_edge_list,
graph
.edge_references()
.map(|edge| (edge.source().index(), edge.target().index()))
.collect::<Vec<(usize, usize)>>(),
)
}
}
2 changes: 2 additions & 0 deletions rustworkx-core/src/generators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod barbell_graph;
mod binomial_tree_graph;
mod complete_graph;
mod cycle_graph;
mod dorogovtsev_goltsev_mendes_graph;
mod full_rary_tree_graph;
mod grid_graph;
mod heavy_hex_graph;
Expand Down Expand Up @@ -48,6 +49,7 @@ pub use barbell_graph::barbell_graph;
pub use binomial_tree_graph::binomial_tree_graph;
pub use complete_graph::complete_graph;
pub use cycle_graph::cycle_graph;
pub use dorogovtsev_goltsev_mendes_graph::dorogovtsev_goltsev_mendes_graph;
pub use full_rary_tree_graph::full_rary_tree_graph;
pub use grid_graph::grid_graph;
pub use heavy_hex_graph::heavy_hex_graph;
Expand Down
1 change: 1 addition & 0 deletions rustworkx/generators/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,4 @@ def directed_complete_graph(
weights: Sequence[Any] | None = ...,
multigraph: bool = ...,
) -> PyDiGraph: ...
def dorogovtsev_goltsev_mendes_graph(n: int) -> PyGraph: ...
42 changes: 42 additions & 0 deletions src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,47 @@ pub fn directed_complete_graph(
})
}

/// Generate a Dorogovtsev-Goltsev-Mendes graph.
///
/// Generate a graph following the recursive procedure in [1]_ .
/// Starting from the two-node, one-edge graph, iterating `n` times generates
/// a graph with `(3**n + 3) // 2` nodes and `3**n` edges.
///
/// :param int n: The number of iterations to perform.
///
/// :returns: The generated Dorogovtsev-Goltsev-Mendes graph
///
/// :rtype: PyGraph
///
/// .. jupyter-execute::
///
/// import rustworkx.generators
/// from rustworkx.visualization import mpl_draw
///
/// graph = rustworkx.generators.dorogovtsev_goltsev_mendes_graph(2)
/// mpl_draw(graph)
///
/// .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes
/// "Pseudofractal scale-free web"
/// Physical Review E 65, 066122, 2002
/// https://arxiv.org/abs/cond-mat/0112143
///
#[pyfunction]
#[pyo3(signature=(n,))]
pub fn dorogovtsev_goltsev_mendes_graph(py: Python, n: usize) -> PyResult<graph::PyGraph> {
let default_fn = || py.None();
let graph = match core_generators::dorogovtsev_goltsev_mendes_graph(n, default_fn, default_fn) {
Ok(graph) => graph,
Err(_) => return Err(PyIndexError::new_err("t must be >= -1")),
};
Ok(graph::PyGraph {
graph,
node_removed: false,
multigraph: false,
attrs: py.None(),
})
}

#[pymodule]
pub fn generators(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(cycle_graph))?;
Expand Down Expand Up @@ -1646,5 +1687,6 @@ pub fn generators(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(directed_empty_graph))?;
m.add_wrapped(wrap_pyfunction!(complete_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_complete_graph))?;
m.add_wrapped(wrap_pyfunction!(dorogovtsev_goltsev_mendes_graph))?;
Ok(())
}
28 changes: 28 additions & 0 deletions tests/generators/test_dorogovtsev_goltsev_mendes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import unittest

import rustworkx


class TestDorogovtsevGoltsevMendesGraph(unittest.TestCase):
def test_dorogovtsev_goltsev_mendes_graph(self):
for n in range(0, 6):
with self.subTest(n=n):
graph = rustworkx.generators.dorogovtsev_goltsev_mendes_graph(n)
self.assertEqual(len(graph), (3**n + 3) // 2)
self.assertEqual(len(graph.edges()), 3**n)
self.assertTrue(rustworkx.is_planar(graph))

def test_dorogovstev_goltsev_mendes_graph_error(self):
self.assertRaises(OverflowError, rustworkx.generators.dorogovtsev_goltsev_mendes_graph, -1)

0 comments on commit c40b169

Please sign in to comment.