Skip to content

Commit cc6229c

Browse files
Adding random regular graph generator for rustworkx-core (#1423)
* adding random regular graph generator * correct spelling rustworkx-core/src/generators/random_graph.rs Co-authored-by: Ivan Carvalho <[email protected]> * Update from HashSet to IndexSet rustworkx-core/src/generators/random_graph.rs Co-authored-by: Ivan Carvalho <[email protected]> * Correct spelling rustworkx-core/src/generators/random_graph.rs Co-authored-by: Ivan Carvalho <[email protected]> * Hashset to indexset rustworkx-core/src/generators/random_graph.rs Co-authored-by: Ivan Carvalho <[email protected]> * Index Set rustworkx-core/src/generators/random_graph.rs Co-authored-by: Ivan Carvalho <[email protected]> * corrected formatting, replaced HashSet and Hashmap to IndexSet and DictMap, added test case * fixing bug * Apply suggestions from code review * Make doctest failure reproducible * fixed bug leading to doctest issue * fix hyperlink --------- Co-authored-by: Ivan Carvalho <[email protected]>
1 parent 74cf811 commit cc6229c

File tree

2 files changed

+181
-1
lines changed

2 files changed

+181
-1
lines changed

rustworkx-core/src/generators/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ pub use random_graph::gnp_random_graph;
6666
pub use random_graph::hyperbolic_random_graph;
6767
pub use random_graph::random_bipartite_graph;
6868
pub use random_graph::random_geometric_graph;
69+
pub use random_graph::random_regular_graph;
6970
pub use random_graph::sbm_random_graph;
7071
pub use star_graph::star_graph;

rustworkx-core/src/generators/random_graph.rs

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#![allow(clippy::float_cmp)]
1414

15+
use crate::dictmap::DictMap;
16+
use indexmap::IndexSet;
1517
use std::hash::Hash;
1618

1719
use ndarray::ArrayView2;
@@ -30,6 +32,171 @@ use rand_pcg::Pcg64;
3032
use super::star_graph;
3133
use super::InvalidInputError;
3234

35+
/// Generates a random regular graph
36+
///
37+
/// A regular graph is one where each node has same number of neighbors. This function takes in
38+
/// number of nodes and degrees as two functions which are used in order to generate a random regular graph.
39+
///
40+
/// This is only defined for undirected graphs, which have no self-directed edges or parallel edges.
41+
///
42+
/// Raises an error if
43+
///
44+
/// This algorithm is based on the implementation of networkx functon
45+
/// <https://github.com/networkx/networkx/blob/networkx-2.4/networkx/generators/random_graphs.py>
46+
///
47+
/// A. Steger and N. Wormald,
48+
/// Generating random regular graphs quickly,
49+
/// Probability and Computing 8 (1999), 377-396, 1999.
50+
/// <https://doi.org/10.1017/S0963548399003867>
51+
///
52+
/// Jeong Han Kim and Van H. Vu,
53+
/// Generating random regular graphs,
54+
/// Proceedings of the thirty-fifth ACM symposium on Theory of computing,
55+
/// San Diego, CA, USA, pp 213--222, 2003.
56+
/// <http://portal.acm.org/citation.cfm?id=780542.780576>
57+
///
58+
/// Arguments:
59+
///
60+
/// * `num_nodes` - The number of nodes for creating the random graph.
61+
/// * `degree` - The number of edges connected to each node.
62+
/// * `seed` - An optional seed to use for the random number generator.
63+
/// * `default_node_weight` - A callable that will return the weight to use
64+
/// for newly created nodes.
65+
/// * `default_edge_weight` - A callable that will return the weight object
66+
/// to use for newly created edges.
67+
/// # Example
68+
/// ```rust
69+
/// use rustworkx_core::petgraph;
70+
/// use rustworkx_core::generators::random_regular_graph;
71+
///
72+
/// let g: petgraph::graph::UnGraph<(), ()> = random_regular_graph(
73+
/// 4,
74+
/// 2,
75+
/// Some(2025),
76+
/// || {()},
77+
/// || {()},
78+
/// ).unwrap();
79+
/// assert_eq!(g.node_count(), 4);
80+
/// assert_eq!(g.edge_count(), 4);
81+
/// ```
82+
pub fn random_regular_graph<G, T, F, H, M>(
83+
num_nodes: usize,
84+
degree: usize,
85+
seed: Option<u64>,
86+
mut default_node_weight: F,
87+
mut default_edge_weight: H,
88+
) -> Result<G, InvalidInputError>
89+
where
90+
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
91+
F: FnMut() -> T,
92+
H: FnMut() -> M,
93+
G::NodeId: Eq + Hash + Copy,
94+
{
95+
if num_nodes == 0 {
96+
return Err(InvalidInputError {});
97+
}
98+
let mut rng: Pcg64 = match seed {
99+
Some(seed) => Pcg64::seed_from_u64(seed),
100+
None => Pcg64::from_entropy(),
101+
};
102+
103+
if (num_nodes * degree) % 2 != 0 {
104+
return Err(InvalidInputError {});
105+
}
106+
107+
if degree >= num_nodes {
108+
return Err(InvalidInputError {});
109+
}
110+
111+
let mut graph = G::with_capacity(num_nodes, num_nodes);
112+
for _ in 0..num_nodes {
113+
graph.add_node(default_node_weight());
114+
}
115+
116+
if degree == 0 {
117+
return Ok(graph);
118+
}
119+
120+
let suitable = |edges: &IndexSet<(G::NodeId, G::NodeId)>,
121+
potential_edges: &DictMap<G::NodeId, usize>|
122+
-> bool {
123+
if potential_edges.is_empty() {
124+
return true;
125+
}
126+
for (s1, _) in potential_edges.iter() {
127+
for (s2, _) in potential_edges.iter() {
128+
if s1 == s2 {
129+
break;
130+
}
131+
let (u, v) = if graph.to_index(*s1) > graph.to_index(*s2) {
132+
(*s2, *s1)
133+
} else {
134+
(*s1, *s2)
135+
};
136+
if !edges.contains(&(u, v)) {
137+
return true;
138+
}
139+
}
140+
}
141+
false
142+
};
143+
144+
let mut try_creation = || -> Option<IndexSet<(G::NodeId, G::NodeId)>> {
145+
let mut edges: IndexSet<(G::NodeId, G::NodeId)> = IndexSet::with_capacity(num_nodes);
146+
let mut stubs: Vec<G::NodeId> = (0..num_nodes)
147+
.flat_map(|x| std::iter::repeat(graph.from_index(x)).take(degree))
148+
.collect();
149+
while !stubs.is_empty() {
150+
let mut potential_edges: DictMap<G::NodeId, usize> = DictMap::default();
151+
stubs.shuffle(&mut rng);
152+
153+
let mut i = 0;
154+
while i + 1 < stubs.len() {
155+
let s1 = stubs[i];
156+
let s2 = stubs[i + 1];
157+
let (u, v) = if graph.to_index(s1) > graph.to_index(s2) {
158+
(s2, s1)
159+
} else {
160+
(s1, s2)
161+
};
162+
if u != v && !edges.contains(&(u, v)) {
163+
edges.insert((u, v));
164+
} else {
165+
*potential_edges.entry(u).or_insert(0) += 1;
166+
*potential_edges.entry(v).or_insert(0) += 1;
167+
}
168+
i += 2;
169+
}
170+
171+
if !suitable(&edges, &potential_edges) {
172+
return None;
173+
}
174+
stubs = Vec::new();
175+
for (key, value) in potential_edges.iter() {
176+
for _ in 0..*value {
177+
stubs.push(*key);
178+
}
179+
}
180+
}
181+
182+
Some(edges)
183+
};
184+
185+
let edges = loop {
186+
match try_creation() {
187+
Some(created_edges) => {
188+
break created_edges;
189+
}
190+
None => continue,
191+
}
192+
};
193+
for (u, v) in edges {
194+
graph.add_edge(u, v, default_edge_weight());
195+
}
196+
197+
Ok(graph)
198+
}
199+
33200
/// Generate a G<sub>np</sub> random graph, also known as an
34201
/// Erdős-Rényi graph or a binomial graph.
35202
///
@@ -884,7 +1051,8 @@ mod tests {
8841051
use crate::generators::InvalidInputError;
8851052
use crate::generators::{
8861053
barabasi_albert_graph, gnm_random_graph, gnp_random_graph, hyperbolic_random_graph,
887-
path_graph, random_bipartite_graph, random_geometric_graph, sbm_random_graph,
1054+
path_graph, random_bipartite_graph, random_geometric_graph, random_regular_graph,
1055+
sbm_random_graph,
8881056
};
8891057
use crate::petgraph;
8901058

@@ -900,6 +1068,17 @@ mod tests {
9001068
assert_eq!(g.edge_count(), 189);
9011069
}
9021070

1071+
#[test]
1072+
fn test_random_regular_graph() {
1073+
let g: petgraph::graph::UnGraph<(), ()> =
1074+
random_regular_graph(4, 2, Some(10), || (), || ()).unwrap();
1075+
assert_eq!(g.node_count(), 4);
1076+
assert_eq!(g.edge_count(), 4);
1077+
for node in g.node_indices() {
1078+
assert_eq!(g.edges(node).count(), 2)
1079+
}
1080+
}
1081+
9031082
#[test]
9041083
fn test_gnp_random_graph_directed_empty() {
9051084
let g: petgraph::graph::DiGraph<(), ()> =

0 commit comments

Comments
 (0)