diff --git a/rustworkx-core/src/generators/binomial_tree_graph.rs b/rustworkx-core/src/generators/binomial_tree_graph.rs new file mode 100644 index 000000000..27837be99 --- /dev/null +++ b/rustworkx-core/src/generators/binomial_tree_graph.rs @@ -0,0 +1,247 @@ +// 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::{Build, Create}; +use petgraph::visit::{Data, EdgeRef, GraphBase, IntoEdgeReferences, NodeIndexable}; + +use super::InvalidInputError; + +/// Generate a binomial tree graph +/// +/// Arguments: +/// +/// * `order` - The order of the binomial tree. +/// * `weights` - A `Vec` of node weight objects. If the number of weights is +/// less than 2**order extra nodes with None will be appended. +/// * `default_node_weight` - A callable that will return the weight to use +/// for newly created nodes. This is ignored if `weights` is specified, +/// as the weights from that argument will be used instead. +/// * `default_edge_weight` - A callable that will return the weight object +/// to use for newly created edges. +/// * `bidirectional` - Whether edges are added bidirectionally, if set to +/// `true` then for any edge `(u, v)` an edge `(v, u)` will also be added. +/// If the graph is undirected this will result in a pallel edge. +/// +/// # Example +/// ```rust +/// use rustworkx_core::petgraph; +/// use rustworkx_core::generators::binomial_tree_graph; +/// use rustworkx_core::petgraph::visit::EdgeRef; +/// +/// let expected_edge_list = vec![ +/// (0, 1), +/// (2, 3), +/// (0, 2), +/// (4, 5), +/// (6, 7), +/// (4, 6), +/// (0, 4), +/// (8, 9), +/// (10, 11), +/// (8, 10), +/// (12, 13), +/// (14, 15), +/// (12, 14), +/// (8, 12), +/// (0, 8), +/// ]; +/// let g: petgraph::graph::UnGraph<(), ()> = binomial_tree_graph( +/// 4, +/// None, +/// || {()}, +/// || {()}, +/// false +/// ).unwrap(); +/// assert_eq!( +/// expected_edge_list, +/// g.edge_references() +/// .map(|edge| (edge.source().index(), edge.target().index())) +/// .collect::>(), +/// ) +/// ``` +pub fn binomial_tree_graph( + order: u32, + weights: Option>, + mut default_node_weight: F, + mut default_edge_weight: H, + bidirectional: bool, +) -> Result +where + G: Build + Create + Data + NodeIndexable, + for<'b> &'b G: GraphBase + IntoEdgeReferences + Copy, + F: FnMut() -> T, + H: FnMut() -> M, + T: Clone, +{ + if order >= 60 { + return Err(InvalidInputError {}); + } + let num_nodes = usize::pow(2, order); + let num_edges = usize::pow(2, order) - 1; + let mut graph = G::with_capacity(num_nodes, num_edges); + + for i in 0..num_nodes { + match weights { + Some(ref weights) => { + if weights.len() > num_nodes { + return Err(InvalidInputError {}); + } + if i < weights.len() { + graph.add_node(weights[i].clone()) + } else { + graph.add_node(default_node_weight()) + } + } + None => graph.add_node(default_node_weight()), + }; + } + + fn find_edge(graph: &mut G, source: usize, target: usize) -> bool + where + G: NodeIndexable, + for<'b> &'b G: GraphBase + IntoEdgeReferences, + { + let mut found = false; + for edge in graph.edge_references() { + if graph.to_index(edge.source()) == source && graph.to_index(edge.target()) == target { + found = true; + break; + } + } + found + } + + let mut n = 1; + let zero_index = 0; + for _ in 0..order { + let edges: Vec<(usize, usize)> = graph + .edge_references() + .map(|e| (graph.to_index(e.source()), graph.to_index(e.target()))) + .collect(); + + for (source, target) in edges { + let source_index = source + n; + let target_index = target + n; + + if !find_edge(&mut graph, source_index, target_index) { + graph.add_edge( + graph.from_index(source_index), + graph.from_index(target_index), + default_edge_weight(), + ); + } + if bidirectional && !find_edge(&mut graph, target_index, source_index) { + graph.add_edge( + graph.from_index(target_index), + graph.from_index(source_index), + default_edge_weight(), + ); + } + } + if !find_edge(&mut graph, zero_index, n) { + graph.add_edge( + graph.from_index(zero_index), + graph.from_index(n), + default_edge_weight(), + ); + } + if bidirectional && !find_edge(&mut graph, n, zero_index) { + graph.add_edge( + graph.from_index(n), + graph.from_index(zero_index), + default_edge_weight(), + ); + } + n *= 2; + } + Ok(graph) +} + +#[cfg(test)] +mod tests { + use crate::generators::binomial_tree_graph; + use crate::generators::InvalidInputError; + use crate::petgraph; + use crate::petgraph::visit::EdgeRef; + + #[test] + fn test_binomial_tree_graph() { + let expected_edge_list = vec![ + (0, 1), + (2, 3), + (0, 2), + (4, 5), + (6, 7), + (4, 6), + (0, 4), + (8, 9), + (10, 11), + (8, 10), + (12, 13), + (14, 15), + (12, 14), + (8, 12), + (0, 8), + ]; + let g: petgraph::graph::UnGraph<(), ()> = + binomial_tree_graph(4, None, || (), || (), false).unwrap(); + assert_eq!( + expected_edge_list, + g.edge_references() + .map(|edge| (edge.source().index(), edge.target().index())) + .collect::>(), + ); + } + + #[test] + fn test_directed_binomial_tree_graph() { + let expected_edge_list = vec![ + (0, 1), + (2, 3), + (0, 2), + (4, 5), + (6, 7), + (4, 6), + (0, 4), + (8, 9), + (10, 11), + (8, 10), + (12, 13), + (14, 15), + (12, 14), + (8, 12), + (0, 8), + ]; + let g: petgraph::graph::DiGraph<(), ()> = + binomial_tree_graph(4, None, || (), || (), false).unwrap(); + assert_eq!( + expected_edge_list, + g.edge_references() + .map(|edge| (edge.source().index(), edge.target().index())) + .collect::>(), + ); + } + + #[test] + fn test_binomial_tree_error() { + match binomial_tree_graph::, (), _, _, ()>( + 75, + None, + || (), + || (), + false, + ) { + Ok(_) => panic!("Returned a non-error"), + Err(e) => assert_eq!(e, InvalidInputError), + }; + } +} diff --git a/rustworkx-core/src/generators/mod.rs b/rustworkx-core/src/generators/mod.rs index 3114b2623..0b2bd138c 100644 --- a/rustworkx-core/src/generators/mod.rs +++ b/rustworkx-core/src/generators/mod.rs @@ -12,6 +12,7 @@ //! This module contains generator functions for building graphs +mod binomial_tree_graph; mod cycle_graph; mod grid_graph; mod heavy_hex_graph; @@ -38,6 +39,7 @@ impl fmt::Display for InvalidInputError { } } +pub use binomial_tree_graph::binomial_tree_graph; pub use cycle_graph::cycle_graph; pub use grid_graph::grid_graph; pub use heavy_hex_graph::heavy_hex_graph; diff --git a/src/generators.rs b/src/generators.rs index 38203fe57..4fe607d24 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -724,48 +724,89 @@ pub fn binomial_tree_graph( order ))); } - let num_nodes = usize::pow(2, order); - let num_edges = usize::pow(2, order) - 1; - let mut graph = StablePyGraph::::with_capacity(num_nodes, num_edges); - for i in 0..num_nodes { - match weights { - Some(ref weights) => { - if weights.len() > num_nodes { - return Err(PyIndexError::new_err("weights should be <= 2**order")); - } - if i < weights.len() { - graph.add_node(weights[i].clone_ref(py)) - } else { - graph.add_node(py.None()) - } + let default_fn = || py.None(); + let graph: StablePyGraph = + match core_generators::binomial_tree_graph(order, weights, default_fn, default_fn, false) { + Ok(graph) => graph, + Err(_) => { + return Err(PyIndexError::new_err( + "num_nodes and weights list not specified", + )) } - None => graph.add_node(py.None()), }; - } - - let mut n = 1; - let zero_index = NodeIndex::new(0); - - for _ in 0..order { - let edges: Vec<(NodeIndex, NodeIndex)> = graph - .edge_references() - .map(|e| (e.source(), e.target())) - .collect(); - for (source, target) in edges { - let source_index = NodeIndex::new(source.index() + n); - let target_index = NodeIndex::new(target.index() + n); - - graph.add_edge(source_index, target_index, py.None()); - } - - graph.add_edge(zero_index, NodeIndex::new(n), py.None()); + Ok(graph::PyGraph { + graph, + node_removed: false, + multigraph, + attrs: py.None(), + }) +} - n *= 2; +/// Generate a directed binomial tree of order n recursively. +/// The edges propagate towards right and bottom direction if ``bidirectional`` is ``false`` +/// +/// :param int order: Order of the binomial tree. The maximum allowed value +/// for order on the platform your running on. If it's a 64bit platform +/// the max value is 59 and on 32bit systems the max value is 29. Any order +/// value above these will raise a ``OverflowError``. +/// :param list weights: A list of node weights. If the number of weights is +/// less than 2**order extra nodes with None will be appended. +/// :param bidirectional: A parameter to indicate if edges should exist in +/// both directions between nodes +/// :param bool multigraph: When set to False the output +/// :class:`~rustworkx.PyDiGraph` object will not be not be a multigraph and +/// won't allow parallel edges to be added. Instead +/// calls which would create a parallel edge will update the existing edge. +/// +/// :returns: A directed binomial tree with 2^n vertices and 2^n - 1 edges. +/// :rtype: PyDiGraph +/// :raises IndexError: If the lenght of ``weights`` is greater that 2^n +/// :raises OverflowError: If the input order exceeds the maximum value for the +/// current platform. +/// +/// .. jupyter-execute:: +/// +/// import rustworkx.generators +/// from rustworkx.visualization import mpl_draw +/// +/// graph = rustworkx.generators.directed_binomial_tree_graph(4) +/// mpl_draw(graph) +/// +#[pyfunction(bidirectional = "false", multigraph = "true")] +#[pyo3(text_signature = "(order, /, weights=None, bidirectional=False, multigraph=True)")] +pub fn directed_binomial_tree_graph( + py: Python, + order: u32, + weights: Option>, + bidirectional: bool, + multigraph: bool, +) -> PyResult { + if order >= MAX_ORDER { + return Err(PyOverflowError::new_err(format!( + "An order of {} exceeds the max allowable size", + order + ))); } - - Ok(graph::PyGraph { + let default_fn = || py.None(); + let graph: StablePyGraph = match core_generators::binomial_tree_graph( + order, + weights, + default_fn, + default_fn, + bidirectional, + ) { + Ok(graph) => graph, + Err(_) => { + return Err(PyIndexError::new_err( + "order and weights list not specified", + )) + } + }; + Ok(digraph::PyDiGraph { graph, node_removed: false, + check_cycle: false, + cycle_state: algo::DfsSpace::default(), multigraph, attrs: py.None(), }) @@ -850,115 +891,6 @@ pub fn full_rary_tree( }) } -/// Generate an undirected binomial tree of order n recursively. -/// The edges propagate towards right and bottom direction if ``bidirectional`` is ``false`` -/// -/// :param int order: Order of the binomial tree. The maximum allowed value -/// for order on the platform your running on. If it's a 64bit platform -/// the max value is 59 and on 32bit systems the max value is 29. Any order -/// value above these will raise a ``OverflowError``. -/// :param list weights: A list of node weights. If the number of weights is -/// less than 2**order extra nodes with None will be appended. -/// :param bidirectional: A parameter to indicate if edges should exist in -/// both directions between nodes -/// :param bool multigraph: When set to False the output -/// :class:`~rustworkx.PyDiGraph` object will not be not be a multigraph and -/// won't allow parallel edges to be added. Instead -/// calls which would create a parallel edge will update the existing edge. -/// -/// :returns: A directed binomial tree with 2^n vertices and 2^n - 1 edges. -/// :rtype: PyDiGraph -/// :raises IndexError: If the lenght of ``weights`` is greater that 2^n -/// :raises OverflowError: If the input order exceeds the maximum value for the -/// current platform. -/// -/// .. jupyter-execute:: -/// -/// import rustworkx.generators -/// from rustworkx.visualization import mpl_draw -/// -/// graph = rustworkx.generators.directed_binomial_tree_graph(4) -/// mpl_draw(graph) -/// -#[pyfunction(bidirectional = "false", multigraph = "true")] -#[pyo3(text_signature = "(order, /, weights=None, bidirectional=False, multigraph=True)")] -pub fn directed_binomial_tree_graph( - py: Python, - order: u32, - weights: Option>, - bidirectional: bool, - multigraph: bool, -) -> PyResult { - if order >= MAX_ORDER { - return Err(PyOverflowError::new_err(format!( - "An order of {} exceeds the max allowable size", - order - ))); - } - let num_nodes = usize::pow(2, order); - let num_edges = usize::pow(2, order) - 1; - let mut graph = StablePyGraph::::with_capacity(num_nodes, num_edges); - - for i in 0..num_nodes { - match weights { - Some(ref weights) => { - if weights.len() > num_nodes { - return Err(PyIndexError::new_err("weights should be <= 2**order")); - } - if i < weights.len() { - graph.add_node(weights[i].clone_ref(py)) - } else { - graph.add_node(py.None()) - } - } - None => graph.add_node(py.None()), - }; - } - - let mut n = 1; - let zero_index = NodeIndex::new(0); - - for _ in 0..order { - let edges: Vec<(NodeIndex, NodeIndex)> = graph - .edge_references() - .map(|e| (e.source(), e.target())) - .collect(); - - for (source, target) in edges { - let source_index = NodeIndex::new(source.index() + n); - let target_index = NodeIndex::new(target.index() + n); - - if graph.find_edge(source_index, target_index).is_none() { - graph.add_edge(source_index, target_index, py.None()); - } - - if bidirectional && graph.find_edge(target_index, source_index).is_none() { - graph.add_edge(target_index, source_index, py.None()); - } - } - let n_index = NodeIndex::new(n); - - if graph.find_edge(zero_index, n_index).is_none() { - graph.add_edge(zero_index, n_index, py.None()); - } - - if bidirectional && graph.find_edge(n_index, zero_index).is_none() { - graph.add_edge(n_index, zero_index, py.None()); - } - - n *= 2; - } - - Ok(digraph::PyDiGraph { - graph, - node_removed: false, - check_cycle: false, - cycle_state: algo::DfsSpace::default(), - multigraph, - attrs: py.None(), - }) -} - /// Generate an undirected heavy square graph. Fig. 6 of /// https://arxiv.org/abs/1907.09528. /// An ASCII diagram of the graph is given by: