diff --git a/rustworkx-core/src/generators/mod.rs b/rustworkx-core/src/generators/mod.rs index b4618ad91..f242b2979 100644 --- a/rustworkx-core/src/generators/mod.rs +++ b/rustworkx-core/src/generators/mod.rs @@ -15,6 +15,7 @@ mod cycle_graph; mod grid_graph; mod path_graph; +mod petersen_graph; mod star_graph; mod utils; @@ -37,4 +38,5 @@ impl fmt::Display for InvalidInputError { pub use cycle_graph::cycle_graph; pub use grid_graph::grid_graph; pub use path_graph::path_graph; +pub use petersen_graph::petersen_graph; pub use star_graph::star_graph; diff --git a/rustworkx-core/src/generators/petersen_graph.rs b/rustworkx-core/src/generators/petersen_graph.rs new file mode 100644 index 000000000..905d2fb0d --- /dev/null +++ b/rustworkx-core/src/generators/petersen_graph.rs @@ -0,0 +1,168 @@ +// 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 std::hash::Hash; + +use petgraph::data::{Build, Create}; +use petgraph::visit::{Data, NodeIndexable}; + +use super::InvalidInputError; + +/// Generate a generalized Petersen graph :math:`G(n, k)` with :math:`2n` +/// nodes and :math:`3n` edges. See Watkins [1]_ for more details. +/// +/// .. note:: +/// +/// The Petersen graph itself is denoted :math:`G(5, 2)` +/// +/// * `n` - Number of nodes in the internal star and external regular polygon. +/// n > 2. +/// * `k` - Shift that changes the internal star graph. k > 0 and 2 * k < n. +/// * `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::petersen_graph; +/// use rustworkx_core::petgraph::visit::EdgeRef; +/// +/// let expected_edge_list = vec![ +/// (0, 2), +/// (1, 3), +/// (2, 4), +/// (3, 0), +/// (4, 1), +/// (5, 6), +/// (6, 7), +/// (7, 8), +/// (8, 9), +/// (9, 5), +/// (5, 0), +/// (6, 1), +/// (7, 2), +/// (8, 3), +/// (9, 4), +/// ]; +/// let g: petgraph::graph::UnGraph<(), ()> = petersen_graph( +/// 5, +/// 2, +/// || {()}, +/// || {()}, +/// ).unwrap(); +/// assert_eq!( +/// expected_edge_list, +/// g.edge_references() +/// .map(|edge| (edge.source().index(), edge.target().index())) +/// .collect::>(), +/// ) +/// ``` +pub fn petersen_graph( + n: usize, + k: usize, + mut default_node_weight: F, + mut default_edge_weight: H, +) -> Result +where + G: Build + Create + Data + NodeIndexable, + F: FnMut() -> T, + H: FnMut() -> M, + G::NodeId: Eq + Hash, +{ + if n < 3 { + return Err(InvalidInputError {}); + } + if k == 0 || 2 * k >= n { + return Err(InvalidInputError {}); + } + + let mut graph = G::with_capacity(2 * n, 3 * n); + + let star_nodes: Vec = (0..n) + .map(|_| graph.add_node(default_node_weight())) + .collect(); + + let polygon_nodes: Vec = (0..n) + .map(|_| graph.add_node(default_node_weight())) + .collect(); + + for i in 0..n { + graph.add_edge( + star_nodes[i], + star_nodes[(i + k) % n], + default_edge_weight(), + ); + } + + for i in 0..n { + graph.add_edge( + polygon_nodes[i], + polygon_nodes[(i + 1) % n], + default_edge_weight(), + ); + } + + for i in 0..n { + graph.add_edge(polygon_nodes[i], star_nodes[i], default_edge_weight()); + } + Ok(graph) +} + +#[cfg(test)] +mod tests { + use crate::generators::petersen_graph; + use crate::generators::InvalidInputError; + use crate::petgraph; + use crate::petgraph::visit::EdgeRef; + + #[test] + fn test_petersen_graph() { + let expected_edge_list = vec![ + (0, 2), + (1, 3), + (2, 4), + (3, 0), + (4, 1), + (5, 6), + (6, 7), + (7, 8), + (8, 9), + (9, 5), + (5, 0), + (6, 1), + (7, 2), + (8, 3), + (9, 4), + ]; + let g: petgraph::graph::UnGraph<(), ()> = petersen_graph(5, 2, || (), || ()).unwrap(); + assert_eq!( + expected_edge_list, + g.edge_references() + .map(|edge| (edge.source().index(), edge.target().index())) + .collect::>(), + ); + } + + #[test] + fn test_petersen_error() { + match petersen_graph::, (), _, _, ()>(2, 3, || (), || ()) { + Ok(_) => panic!("Returned a non-error"), + Err(e) => assert_eq!(e, InvalidInputError), + }; + } +} diff --git a/src/generators.rs b/src/generators.rs index acba83abf..a83f54a17 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1997,34 +1997,16 @@ pub fn generalized_petersen_graph( k: usize, multigraph: bool, ) -> PyResult { - if n < 3 { - return Err(PyIndexError::new_err("n must be at least 3")); - } - - if k == 0 || 2 * k >= n { - return Err(PyIndexError::new_err( - "k is invalid: it must be positive and less than n/2", - )); - } - - let mut graph = StablePyGraph::::with_capacity(2 * n, 3 * n); - - let star_nodes: Vec = (0..n).map(|_| graph.add_node(py.None())).collect(); - - let polygon_nodes: Vec = (0..n).map(|_| graph.add_node(py.None())).collect(); - - for i in 0..n { - graph.add_edge(star_nodes[i], star_nodes[(i + k) % n], py.None()); - } - - for i in 0..n { - graph.add_edge(polygon_nodes[i], polygon_nodes[(i + 1) % n], py.None()); - } - - for i in 0..n { - graph.add_edge(polygon_nodes[i], star_nodes[i], py.None()); - } - + let default_fn = || py.None(); + let graph: StablePyGraph = + match core_generators::petersen_graph(n, k, default_fn, default_fn) { + Ok(graph) => graph, + Err(_) => { + return Err(PyIndexError::new_err( + "n > 2, k > 0, or 2 * k > n not satisfied.", + )) + } + }; Ok(graph::PyGraph { graph, node_removed: false,