• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / rustworkx / 16921855851

12 Aug 2025 09:56PM UTC coverage: 94.642% (-0.01%) from 94.653%
16921855851

push

github

web-flow
Make `rustworkx` build and run with pyiodide (#1447)

* Pyodide: force only one thread

* Add .cargo/config.toml

* Unstable feature that makes rayon work

* Newline

* Document pyodide

* Add release note for pyodide

* Fix separator

* Consolidate the bare minimum of flags

* Add emscripten-wasm-eh flag

* Solve nit

* Remove flags forcing emscripten builds from requiring nightly

17805 of 18813 relevant lines covered (94.64%)

1013349.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.34
/rustworkx-core/src/generators/random_graph.rs
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
#![allow(clippy::float_cmp)]
14

15
use crate::dictmap::DictMap;
16
use indexmap::IndexSet;
17
use std::hash::Hash;
18

19
use ndarray::ArrayView2;
20
use petgraph::data::{Build, Create};
21
use petgraph::visit::{
22
    Data, EdgeRef, GraphBase, GraphProp, IntoEdgeReferences, IntoEdgesDirected,
23
    IntoNodeIdentifiers, NodeCount, NodeIndexable,
24
};
25
use petgraph::{Incoming, Outgoing};
26

27
use rand::prelude::*;
28
use rand_distr::{Distribution, Uniform};
29
use rand_pcg::Pcg64;
30

31
use super::star_graph;
32
use super::InvalidInputError;
33

34
/// Generates a random regular graph
35
///
36
/// A regular graph is one where each node has same number of neighbors. This function takes in
37
/// number of nodes and degrees as two functions which are used in order to generate a random regular graph.
38
///
39
/// This is only defined for undirected graphs, which have no self-directed edges or parallel edges.
40
///
41
/// Raises an error if
42
///
43
/// This algorithm is based on the implementation of networkx function
44
/// <https://github.com/networkx/networkx/blob/networkx-2.4/networkx/generators/random_graphs.py>
45
///
46
/// A. Steger and N. Wormald,
47
/// Generating random regular graphs quickly,
48
/// Probability and Computing 8 (1999), 377-396, 1999.
49
/// <https://doi.org/10.1017/S0963548399003867>
50
///
51
/// Jeong Han Kim and Van H. Vu,
52
/// Generating random regular graphs,
53
/// Proceedings of the thirty-fifth ACM symposium on Theory of computing,
54
/// San Diego, CA, USA, pp 213--222, 2003.
55
/// <http://portal.acm.org/citation.cfm?id=780542.780576>
56
///
57
/// Arguments:
58
///
59
/// * `num_nodes` - The number of nodes for creating the random graph.
60
/// * `degree` - The number of edges connected to each node.
61
/// * `seed` - An optional seed to use for the random number generator.
62
/// * `default_node_weight` - A callable that will return the weight to use
63
///   for newly created nodes.
64
/// * `default_edge_weight` - A callable that will return the weight object
65
///   to use for newly created edges.
66
/// # Example
67
/// ```rust
68
/// use rustworkx_core::petgraph;
69
/// use rustworkx_core::generators::random_regular_graph;
70
///
71
/// let g: petgraph::graph::UnGraph<(), ()> = random_regular_graph(
72
///     4,
73
///     2,
74
///     Some(2025),
75
///     || {()},
76
///     || {()},
77
/// ).unwrap();
78
/// assert_eq!(g.node_count(), 4);
79
/// assert_eq!(g.edge_count(), 4);
80
/// ```
81
pub fn random_regular_graph<G, T, F, H, M>(
×
82
    num_nodes: usize,
×
83
    degree: usize,
×
84
    seed: Option<u64>,
×
85
    mut default_node_weight: F,
×
86
    mut default_edge_weight: H,
×
87
) -> Result<G, InvalidInputError>
×
88
where
×
89
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
×
90
    F: FnMut() -> T,
×
91
    H: FnMut() -> M,
×
92
    G::NodeId: Eq + Hash + Copy,
×
93
{
94
    if num_nodes == 0 {
×
95
        return Err(InvalidInputError {});
×
96
    }
×
97
    let mut rng: Pcg64 = match seed {
×
98
        Some(seed) => Pcg64::seed_from_u64(seed),
×
99
        None => Pcg64::from_os_rng(),
×
100
    };
101

102
    if (num_nodes * degree) % 2 != 0 {
×
103
        return Err(InvalidInputError {});
×
104
    }
×
105

106
    if degree >= num_nodes {
×
107
        return Err(InvalidInputError {});
×
108
    }
×
109

110
    let mut graph = G::with_capacity(num_nodes, num_nodes);
×
111
    for _ in 0..num_nodes {
×
112
        graph.add_node(default_node_weight());
×
113
    }
×
114

115
    if degree == 0 {
×
116
        return Ok(graph);
×
117
    }
×
118

119
    let suitable = |edges: &IndexSet<(G::NodeId, G::NodeId)>,
×
120
                    potential_edges: &DictMap<G::NodeId, usize>|
121
     -> bool {
×
122
        if potential_edges.is_empty() {
×
123
            return true;
×
124
        }
×
125
        for (s1, _) in potential_edges.iter() {
×
126
            for (s2, _) in potential_edges.iter() {
×
127
                if s1 == s2 {
×
128
                    break;
×
129
                }
×
130
                let (u, v) = if graph.to_index(*s1) > graph.to_index(*s2) {
×
131
                    (*s2, *s1)
×
132
                } else {
133
                    (*s1, *s2)
×
134
                };
135
                if !edges.contains(&(u, v)) {
×
136
                    return true;
×
137
                }
×
138
            }
139
        }
140
        false
×
141
    };
×
142

143
    let mut try_creation = || -> Option<IndexSet<(G::NodeId, G::NodeId)>> {
×
144
        let mut edges: IndexSet<(G::NodeId, G::NodeId)> = IndexSet::with_capacity(num_nodes);
×
145
        let mut stubs: Vec<G::NodeId> = (0..num_nodes)
×
146
            .flat_map(|x| std::iter::repeat(graph.from_index(x)).take(degree))
×
147
            .collect();
×
148
        while !stubs.is_empty() {
×
149
            let mut potential_edges: DictMap<G::NodeId, usize> = DictMap::default();
×
150
            stubs.shuffle(&mut rng);
×
151

152
            let mut i = 0;
×
153
            while i + 1 < stubs.len() {
×
154
                let s1 = stubs[i];
×
155
                let s2 = stubs[i + 1];
×
156
                let (u, v) = if graph.to_index(s1) > graph.to_index(s2) {
×
157
                    (s2, s1)
×
158
                } else {
159
                    (s1, s2)
×
160
                };
161
                if u != v && !edges.contains(&(u, v)) {
×
162
                    edges.insert((u, v));
×
163
                } else {
×
164
                    *potential_edges.entry(u).or_insert(0) += 1;
×
165
                    *potential_edges.entry(v).or_insert(0) += 1;
×
166
                }
×
167
                i += 2;
×
168
            }
169

170
            if !suitable(&edges, &potential_edges) {
×
171
                return None;
×
172
            }
×
173
            stubs = Vec::new();
×
174
            for (key, value) in potential_edges.iter() {
×
175
                for _ in 0..*value {
×
176
                    stubs.push(*key);
×
177
                }
×
178
            }
179
        }
180

181
        Some(edges)
×
182
    };
×
183

184
    let edges = loop {
×
185
        match try_creation() {
×
186
            Some(created_edges) => {
×
187
                break created_edges;
×
188
            }
189
            None => continue,
×
190
        }
191
    };
192
    for (u, v) in edges {
×
193
        graph.add_edge(u, v, default_edge_weight());
×
194
    }
×
195

196
    Ok(graph)
×
197
}
×
198

199
/// Generate a G<sub>np</sub> random graph, also known as an
200
/// Erdős-Rényi graph or a binomial graph.
201
///
202
/// For number of nodes `n` and probability `p`, the G<sub>np</sub>
203
/// graph algorithm creates `n` nodes, and for all the `n * (n - 1)` possible edges,
204
/// each edge is created independently with probability `p`.
205
/// In general, for any probability `p`, the expected number of edges returned
206
/// is `m = p * n * (n - 1)`. If `p = 0` or `p = 1`, the returned
207
/// graph is not random and will always be an empty or a complete graph respectively.
208
/// An empty graph has zero edges and a complete directed graph has `n (n - 1)` edges.
209
/// The run time is `O(n + m)` where `m` is the expected number of edges mentioned above.
210
/// When `p = 0`, run time always reduces to `O(n)`, as the lower bound.
211
/// When `p = 1`, run time always goes to `O(n + n * (n - 1))`, as the upper bound.
212
///
213
/// For `0 < p < 1`, the algorithm is based on the implementation of the networkx function
214
/// ``fast_gnp_random_graph``,
215
/// <https://github.com/networkx/networkx/blob/networkx-2.4/networkx/generators/random_graphs.py#L49-L120>
216
///
217
/// Vladimir Batagelj and Ulrik Brandes,
218
///    "Efficient generation of large random networks",
219
///    Phys. Rev. E, 71, 036113, 2005.
220
///
221
/// Arguments:
222
///
223
/// * `num_nodes` - The number of nodes for creating the random graph.
224
/// * `probability` - The probability of creating an edge between two nodes as a float.
225
/// * `seed` - An optional seed to use for the random number generator.
226
/// * `default_node_weight` - A callable that will return the weight to use
227
///   for newly created nodes.
228
/// * `default_edge_weight` - A callable that will return the weight object
229
///   to use for newly created edges.
230
///
231
/// # Example
232
/// ```rust
233
/// use rustworkx_core::petgraph;
234
/// use rustworkx_core::generators::gnp_random_graph;
235
///
236
/// let g: petgraph::graph::DiGraph<(), ()> = gnp_random_graph(
237
///     20,
238
///     1.0,
239
///     None,
240
///     || {()},
241
///     || {()},
242
/// ).unwrap();
243
/// assert_eq!(g.node_count(), 20);
244
/// assert_eq!(g.edge_count(), 20 * (20 - 1));
245
/// ```
246
pub fn gnp_random_graph<G, T, F, H, M>(
10,346✔
247
    num_nodes: usize,
10,346✔
248
    probability: f64,
10,346✔
249
    seed: Option<u64>,
10,346✔
250
    mut default_node_weight: F,
10,346✔
251
    mut default_edge_weight: H,
10,346✔
252
) -> Result<G, InvalidInputError>
10,346✔
253
where
10,346✔
254
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
10,346✔
255
    F: FnMut() -> T,
10,346✔
256
    H: FnMut() -> M,
10,346✔
257
    G::NodeId: Eq + Hash,
10,346✔
258
{
259
    if num_nodes == 0 {
10,346✔
260
        return Err(InvalidInputError {});
4✔
261
    }
10,342✔
262
    let mut rng: Pcg64 = match seed {
10,342✔
263
        Some(seed) => Pcg64::seed_from_u64(seed),
10,326✔
264
        None => Pcg64::from_os_rng(),
16✔
265
    };
266
    let mut graph = G::with_capacity(num_nodes, num_nodes);
10,342✔
267
    let directed = graph.is_directed();
10,342✔
268

269
    for _ in 0..num_nodes {
103,874✔
270
        graph.add_node(default_node_weight());
103,874✔
271
    }
103,874✔
272
    if !(0.0..=1.0).contains(&probability) {
10,342✔
273
        return Err(InvalidInputError {});
4✔
274
    }
10,338✔
275

276
    if probability > 0.0 {
10,338✔
277
        if (probability - 1.0).abs() < f64::EPSILON {
10,334✔
278
            for u in 0..num_nodes {
80✔
279
                let start_node = if directed { 0 } else { u + 1 };
80✔
280
                for v in start_node..num_nodes {
1,180✔
281
                    if !directed || u != v {
1,180✔
282
                        // exclude self-loops
1,140✔
283
                        let u_index = graph.from_index(u);
1,140✔
284
                        let v_index = graph.from_index(v);
1,140✔
285
                        graph.add_edge(u_index, v_index, default_edge_weight());
1,140✔
286
                    }
1,140✔
287
                }
288
            }
289
        } else {
290
            let num_nodes: isize = match num_nodes.try_into() {
10,330✔
291
                Ok(nodes) => nodes,
10,330✔
292
                Err(_) => return Err(InvalidInputError {}),
×
293
            };
294
            let lp: f64 = (1.0 - probability).ln();
10,330✔
295
            let between = Uniform::new(0.0, 1.0).unwrap();
10,330✔
296

297
            // For directed, create inward edges to a v
298
            if directed {
10,330✔
299
                let mut v: isize = 0;
46✔
300
                let mut w: isize = -1;
46✔
301
                while v < num_nodes {
10,552✔
302
                    let random: f64 = between.sample(&mut rng);
10,506✔
303
                    let lr: f64 = (1.0 - random).ln();
10,506✔
304
                    w = w + 1 + ((lr / lp) as isize);
10,506✔
305
                    while w >= v && v < num_nodes {
11,144✔
306
                        w -= v;
638✔
307
                        v += 1;
638✔
308
                    }
638✔
309
                    // Skip self-loops
310
                    if v == w {
10,506✔
311
                        w -= v;
×
312
                        v += 1;
×
313
                    }
10,506✔
314
                    if v < num_nodes {
10,506✔
315
                        let v_index = graph.from_index(v as usize);
10,460✔
316
                        let w_index = graph.from_index(w as usize);
10,460✔
317
                        graph.add_edge(w_index, v_index, default_edge_weight());
10,460✔
318
                    }
10,460✔
319
                }
320
            }
10,284✔
321

322
            // For directed and undirected, create outward edges from a v
323
            // Nodes in graph are from 0,n-1 (start with v as the second node index).
324
            let mut v: isize = 1;
10,330✔
325
            let mut w: isize = -1;
10,330✔
326
            while v < num_nodes {
388,136✔
327
                let random: f64 = between.sample(&mut rng);
377,806✔
328
                let lr: f64 = (1.0 - random).ln();
377,806✔
329
                w = w + 1 + ((lr / lp) as isize);
377,806✔
330
                while w >= v && v < num_nodes {
471,098✔
331
                    w -= v;
93,292✔
332
                    v += 1;
93,292✔
333
                }
93,292✔
334
                if v < num_nodes {
377,806✔
335
                    let v_index = graph.from_index(v as usize);
367,476✔
336
                    let w_index = graph.from_index(w as usize);
367,476✔
337
                    graph.add_edge(v_index, w_index, default_edge_weight());
367,476✔
338
                }
367,476✔
339
            }
340
        }
341
    }
4✔
342
    Ok(graph)
10,338✔
343
}
10,346✔
344

345
// /// Return a `G_{nm}` directed graph, also known as an
346
// /// Erdős-Rényi graph.
347
// ///
348
// /// Generates a random directed graph out of all the possible graphs with `n` nodes and
349
// /// `m` edges. The generated graph will not be a multigraph and will not have self loops.
350
// ///
351
// /// For `n` nodes, the maximum edges that can be returned is `n (n - 1)`.
352
// /// Passing `m` higher than that will still return the maximum number of edges.
353
// /// If `m = 0`, the returned graph will always be empty (no edges).
354
// /// When a seed is provided, the results are reproducible. Passing a seed when `m = 0`
355
// /// or `m >= n (n - 1)` has no effect, as the result will always be an empty or a complete graph respectively.
356
// ///
357
// /// This algorithm has a time complexity of `O(n + m)`
358

359
/// Generate a G<sub>nm</sub> random graph, also known as an
360
/// Erdős-Rényi graph.
361
///
362
/// Generates a random directed graph out of all the possible graphs with `n` nodes and
363
/// `m` edges. The generated graph will not be a multigraph and will not have self loops.
364
///
365
/// For `n` nodes, the maximum edges that can be returned is `n * (n - 1)`.
366
/// Passing `m` higher than that will still return the maximum number of edges.
367
/// If `m = 0`, the returned graph will always be empty (no edges).
368
/// When a seed is provided, the results are reproducible. Passing a seed when `m = 0`
369
/// or `m >= n * (n - 1)` has no effect, as the result will always be an empty or a
370
/// complete graph respectively.
371
///
372
/// This algorithm has a time complexity of `O(n + m)`
373
///
374
/// Arguments:
375
///
376
/// * `num_nodes` - The number of nodes to create in the graph.
377
/// * `num_edges` - The number of edges to create in the graph.
378
/// * `seed` - An optional seed to use for the random number generator.
379
/// * `default_node_weight` - A callable that will return the weight to use
380
///   for newly created nodes.
381
/// * `default_edge_weight` - A callable that will return the weight object
382
///   to use for newly created edges.
383
///
384
/// # Example
385
/// ```rust
386
/// use rustworkx_core::petgraph;
387
/// use rustworkx_core::generators::gnm_random_graph;
388
///
389
/// let g: petgraph::graph::DiGraph<(), ()> = gnm_random_graph(
390
///     20,
391
///     12,
392
///     None,
393
///     || {()},
394
///     || {()},
395
/// ).unwrap();
396
/// assert_eq!(g.node_count(), 20);
397
/// assert_eq!(g.edge_count(), 12);
398
/// ```
399
pub fn gnm_random_graph<G, T, F, H, M>(
58✔
400
    num_nodes: usize,
58✔
401
    num_edges: usize,
58✔
402
    seed: Option<u64>,
58✔
403
    mut default_node_weight: F,
58✔
404
    mut default_edge_weight: H,
58✔
405
) -> Result<G, InvalidInputError>
58✔
406
where
58✔
407
    G: GraphProp + Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
58✔
408
    F: FnMut() -> T,
58✔
409
    H: FnMut() -> M,
58✔
410
    for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
58✔
411
    G::NodeId: Eq + Hash,
58✔
412
{
413
    if num_nodes == 0 {
58✔
414
        return Err(InvalidInputError {});
4✔
415
    }
54✔
416

417
    fn find_edge<G>(graph: &G, source: usize, target: usize) -> bool
4,228✔
418
    where
4,228✔
419
        G: GraphBase + NodeIndexable,
4,228✔
420
        for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
4,228✔
421
    {
422
        let mut found = false;
4,228✔
423
        for edge in graph.edge_references() {
1,142,814✔
424
            if graph.to_index(edge.source()) == source && graph.to_index(edge.target()) == target {
1,142,814✔
425
                found = true;
340✔
426
                break;
340✔
427
            }
1,142,474✔
428
        }
429
        found
4,228✔
430
    }
4,228✔
431

432
    let mut rng: Pcg64 = match seed {
54✔
433
        Some(seed) => Pcg64::seed_from_u64(seed),
20✔
434
        None => Pcg64::from_os_rng(),
34✔
435
    };
436
    let mut graph = G::with_capacity(num_nodes, num_edges);
54✔
437
    let directed = graph.is_directed();
54✔
438

439
    for _ in 0..num_nodes {
1,132✔
440
        graph.add_node(default_node_weight());
1,132✔
441
    }
1,132✔
442
    // if number of edges to be created is >= max,
443
    // avoid randomly missed trials and directly add edges between every node
444
    let div_by = if directed { 1 } else { 2 };
54✔
445
    if num_edges >= num_nodes * (num_nodes - 1) / div_by {
54✔
446
        for u in 0..num_nodes {
246✔
447
            let start_node = if directed { 0 } else { u + 1 };
246✔
448
            for v in start_node..num_nodes {
3,546✔
449
                // avoid self-loops
450
                if !directed || u != v {
3,546✔
451
                    let u_index = graph.from_index(u);
3,426✔
452
                    let v_index = graph.from_index(v);
3,426✔
453
                    graph.add_edge(u_index, v_index, default_edge_weight());
3,426✔
454
                }
3,426✔
455
            }
456
        }
457
    } else {
458
        let mut created_edges: usize = 0;
40✔
459
        let between = Uniform::new(0, num_nodes).unwrap();
40✔
460
        while created_edges < num_edges {
4,394✔
461
            let u = between.sample(&mut rng);
4,354✔
462
            let v = between.sample(&mut rng);
4,354✔
463
            let u_index = graph.from_index(u);
4,354✔
464
            let v_index = graph.from_index(v);
4,354✔
465
            // avoid self-loops and multi-graphs
466
            if u != v && !find_edge(&graph, u, v) {
4,354✔
467
                graph.add_edge(u_index, v_index, default_edge_weight());
3,888✔
468
                created_edges += 1;
3,888✔
469
            }
3,888✔
470
        }
471
    }
472
    Ok(graph)
54✔
473
}
58✔
474

475
/// Generate a graph from the stochastic block model.
476
///
477
/// The stochastic block model is a generalization of the G<sub>np</sub> random graph
478
/// (see [gnp_random_graph] ). The connection probability of
479
/// nodes `u` and `v` depends on their block and is given by
480
/// `probabilities[blocks[u]][blocks[v]]`, where `blocks[u]` is the block membership
481
/// of vertex `u`. The number of nodes and the number of blocks are inferred from
482
/// `sizes`.
483
///
484
/// Arguments:
485
///
486
/// * `sizes` - Number of nodes in each block.
487
/// * `probabilities` - B x B array that contains the connection probability between
488
///   nodes of different blocks. Must be symmetric for undirected graphs.
489
/// * `loops` - Determines whether the graph can have loops or not.
490
/// * `seed` - An optional seed to use for the random number generator.
491
/// * `default_node_weight` - A callable that will return the weight to use
492
///   for newly created nodes.
493
/// * `default_edge_weight` - A callable that will return the weight object
494
///   to use for newly created edges.
495
///
496
/// # Example
497
/// ```rust
498
/// use ndarray::arr2;
499
/// use rustworkx_core::petgraph;
500
/// use rustworkx_core::generators::sbm_random_graph;
501
///
502
/// let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
503
///     &[1, 2],
504
///     &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
505
///     true,
506
///     Some(10),
507
///     || (),
508
///     || (),
509
/// )
510
/// .unwrap();
511
/// assert_eq!(g.node_count(), 3);
512
/// assert_eq!(g.edge_count(), 6);
513
/// ```
514
pub fn sbm_random_graph<G, T, F, H, M>(
26✔
515
    sizes: &[usize],
26✔
516
    probabilities: &ndarray::ArrayView2<f64>,
26✔
517
    loops: bool,
26✔
518
    seed: Option<u64>,
26✔
519
    mut default_node_weight: F,
26✔
520
    mut default_edge_weight: H,
26✔
521
) -> Result<G, InvalidInputError>
26✔
522
where
26✔
523
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
26✔
524
    F: FnMut() -> T,
26✔
525
    H: FnMut() -> M,
26✔
526
    G::NodeId: Eq + Hash,
26✔
527
{
528
    let num_nodes: usize = sizes.iter().sum();
26✔
529
    if num_nodes == 0 {
26✔
530
        return Err(InvalidInputError {});
4✔
531
    }
22✔
532
    let num_communities = sizes.len();
22✔
533
    if probabilities.nrows() != num_communities
22✔
534
        || probabilities.ncols() != num_communities
20✔
535
        || probabilities.iter().any(|&x| !(0. ..=1.).contains(&x))
60✔
536
    {
537
        return Err(InvalidInputError {});
12✔
538
    }
10✔
539

540
    let mut graph = G::with_capacity(num_nodes, num_nodes);
10✔
541
    let directed = graph.is_directed();
10✔
542
    if !directed && !symmetric_array(probabilities) {
10✔
543
        return Err(InvalidInputError {});
2✔
544
    }
8✔
545

546
    for _ in 0..num_nodes {
24✔
547
        graph.add_node(default_node_weight());
24✔
548
    }
24✔
549
    let mut rng: Pcg64 = match seed {
8✔
550
        Some(seed) => Pcg64::seed_from_u64(seed),
×
551
        None => Pcg64::from_os_rng(),
8✔
552
    };
553
    let mut blocks = Vec::new();
8✔
554
    {
555
        let mut block = 0;
8✔
556
        let mut vertices_left = sizes[0];
8✔
557
        for _ in 0..num_nodes {
8✔
558
            while vertices_left == 0 {
32✔
559
                block += 1;
8✔
560
                vertices_left = sizes[block];
8✔
561
            }
8✔
562
            blocks.push(block);
24✔
563
            vertices_left -= 1;
24✔
564
        }
565
    }
566

567
    let between = Uniform::new(0.0, 1.0).unwrap();
8✔
568
    for v in 0..(if directed || loops {
22✔
569
        num_nodes
6✔
570
    } else {
571
        num_nodes - 1
2✔
572
    }) {
573
        for w in ((if directed { 0 } else { v })..num_nodes).filter(|&w| w != v || loops) {
58✔
574
            if &between.sample(&mut rng)
48✔
575
                < probabilities.get((blocks[v], blocks[w])).unwrap_or(&0_f64)
48✔
576
            {
26✔
577
                graph.add_edge(
26✔
578
                    graph.from_index(v),
26✔
579
                    graph.from_index(w),
26✔
580
                    default_edge_weight(),
26✔
581
                );
26✔
582
            }
26✔
583
        }
584
    }
585
    Ok(graph)
8✔
586
}
26✔
587

588
fn symmetric_array<T: std::cmp::PartialEq>(mat: &ArrayView2<T>) -> bool {
6✔
589
    let n = mat.nrows();
6✔
590
    for (i, row) in mat.rows().into_iter().enumerate().take(n - 1) {
6✔
591
        for (j, m_ij) in row.iter().enumerate().skip(i + 1) {
6✔
592
            if m_ij != mat.get((j, i)).unwrap() {
6✔
593
                return false;
2✔
594
            }
4✔
595
        }
596
    }
597
    true
4✔
598
}
6✔
599

600
#[inline]
601
fn pnorm(x: f64, p: f64) -> f64 {
×
602
    if p == 1.0 || p == f64::INFINITY {
×
603
        x.abs()
×
604
    } else if p == 2.0 {
×
605
        x * x
×
606
    } else {
607
        x.abs().powf(p)
×
608
    }
609
}
×
610

611
fn distance(x: &[f64], y: &[f64], p: f64) -> f64 {
×
612
    let it = x.iter().zip(y.iter()).map(|(xi, yi)| pnorm(xi - yi, p));
×
613

614
    if p == f64::INFINITY {
×
615
        it.fold(-1.0, |max, x| if x > max { x } else { max })
×
616
    } else {
617
        it.sum()
×
618
    }
619
}
×
620

621
/// Generate a random geometric graph in the unit cube of dimensions `dim`.
622
///
623
/// The random geometric graph model places `num_nodes` nodes uniformly at
624
/// random in the unit cube. Two nodes are joined by an edge if the
625
/// distance between the nodes is at most `radius`.
626
///
627
/// Each node has a node attribute ``'pos'`` that stores the
628
/// position of that node in Euclidean space as provided by the
629
/// ``pos`` keyword argument or, if ``pos`` was not provided, as
630
/// generated by this function.
631
///
632
/// Arguments
633
///
634
/// * `num_nodes` - The number of nodes to create in the graph.
635
/// * `radius` - Distance threshold value.
636
/// * `dim` - Dimension of node positions. Default: 2
637
/// * `pos` - Optional list with node positions as values.
638
/// * `p` - Which Minkowski distance metric to use.  `p` has to meet the condition
639
///   ``1 <= p <= infinity``.
640
///   If this argument is not specified, the L<sup>2</sup> metric
641
///   (the Euclidean distance metric), `p = 2` is used.
642
/// * `seed` - An optional seed to use for the random number generator.
643
/// * `default_edge_weight` - A callable that will return the weight object
644
///   to use for newly created edges.
645
///
646
/// # Example
647
/// ```rust
648
/// use rustworkx_core::petgraph;
649
/// use rustworkx_core::generators::random_geometric_graph;
650
///
651
/// let g: petgraph::graph::UnGraph<Vec<f64>, ()> = random_geometric_graph(
652
///     10,
653
///     1.42,
654
///     2,
655
///     None,
656
///     2.0,
657
///     None,
658
///     || {()},
659
/// ).unwrap();
660
/// assert_eq!(g.node_count(), 10);
661
/// assert_eq!(g.edge_count(), 45);
662
/// ```
663
pub fn random_geometric_graph<G, H, M>(
×
664
    num_nodes: usize,
×
665
    radius: f64,
×
666
    dim: usize,
×
667
    pos: Option<Vec<Vec<f64>>>,
×
668
    p: f64,
×
669
    seed: Option<u64>,
×
670
    mut default_edge_weight: H,
×
671
) -> Result<G, InvalidInputError>
×
672
where
×
673
    G: GraphBase + Build + Create + Data<NodeWeight = Vec<f64>, EdgeWeight = M> + NodeIndexable,
×
674
    H: FnMut() -> M,
×
675
    for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
×
676
    G::NodeId: Eq + Hash,
×
677
{
678
    if num_nodes == 0 {
×
679
        return Err(InvalidInputError {});
×
680
    }
×
681
    let mut rng: Pcg64 = match seed {
×
682
        Some(seed) => Pcg64::seed_from_u64(seed),
×
683
        None => Pcg64::from_os_rng(),
×
684
    };
685
    let mut graph = G::with_capacity(num_nodes, num_nodes);
×
686

687
    let radius_p = pnorm(radius, p);
×
688
    let dist = Uniform::new(0.0, 1.0).unwrap();
×
689
    let pos = pos.unwrap_or_else(|| {
×
690
        (0..num_nodes)
×
691
            .map(|_| (0..dim).map(|_| dist.sample(&mut rng)).collect())
×
692
            .collect()
×
693
    });
×
694
    if num_nodes != pos.len() {
×
695
        return Err(InvalidInputError {});
×
696
    }
×
697
    for pval in pos.iter() {
×
698
        graph.add_node(pval.clone());
×
699
    }
×
700
    for u in 0..(num_nodes - 1) {
×
701
        for v in (u + 1)..num_nodes {
×
702
            if distance(&pos[u], &pos[v], p) < radius_p {
×
703
                graph.add_edge(
×
704
                    graph.from_index(u),
×
705
                    graph.from_index(v),
×
706
                    default_edge_weight(),
×
707
                );
×
708
            }
×
709
        }
710
    }
711
    Ok(graph)
×
712
}
×
713

714
/// Generate a random Barabási–Albert preferential attachment algorithm
715
///
716
/// A graph of `n` nodes is grown by attaching new nodes each with `m`
717
/// edges that are preferentially attached to existing nodes with high degree.
718
/// If the graph is directed for the purposes of the extension algorithm all
719
/// edges are treated as weak (meaning both incoming and outgoing).
720
///
721
/// The algorithm implemented in this function is described in:
722
///
723
/// A. L. Barabási and R. Albert "Emergence of scaling in random networks",
724
/// Science 286, pp 509-512, 1999.
725
///
726
/// Arguments:
727
///
728
/// * `n` - The number of nodes to extend the graph to.
729
/// * `m` - The number of edges to attach from a new node to existing nodes.
730
/// * `seed` - An optional seed to use for the random number generator.
731
/// * `initial_graph` - An optional starting graph to expand, if not specified
732
///   a star graph of `m` nodes is generated and used. If specified the input
733
///   graph is mutated by this function and is expected to be moved into this
734
///   function.
735
/// * `default_node_weight` - A callable that will return the weight to use
736
///   for newly created nodes.
737
/// * `default_edge_weight` - A callable that will return the weight object
738
///   to use for newly created edges.
739
///
740
/// An `InvalidInput` error is returned under the following conditions. If `m < 1`
741
/// or `m >= n` and if an `initial_graph` is specified and the number of nodes in
742
/// `initial_graph` is `< m` or `> n`.
743
///
744
/// # Example
745
/// ```rust
746
/// use rustworkx_core::petgraph;
747
/// use rustworkx_core::generators::barabasi_albert_graph;
748
/// use rustworkx_core::generators::star_graph;
749
///
750
/// let graph: petgraph::graph::UnGraph<(), ()> = barabasi_albert_graph(
751
///     20,
752
///     12,
753
///     Some(42),
754
///     None,
755
///     || {()},
756
///     || {()},
757
/// ).unwrap();
758
/// assert_eq!(graph.node_count(), 20);
759
/// assert_eq!(graph.edge_count(), 107);
760
/// ```
761
pub fn barabasi_albert_graph<G, T, F, H, M>(
12✔
762
    n: usize,
12✔
763
    m: usize,
12✔
764
    seed: Option<u64>,
12✔
765
    initial_graph: Option<G>,
12✔
766
    mut default_node_weight: F,
12✔
767
    mut default_edge_weight: H,
12✔
768
) -> Result<G, InvalidInputError>
12✔
769
where
12✔
770
    G: Data<NodeWeight = T, EdgeWeight = M>
12✔
771
        + NodeIndexable
12✔
772
        + GraphProp
12✔
773
        + NodeCount
12✔
774
        + Build
12✔
775
        + Create,
12✔
776
    for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgesDirected + IntoNodeIdentifiers,
12✔
777
    F: FnMut() -> T,
12✔
778
    H: FnMut() -> M,
12✔
779
    G::NodeId: Eq + Hash,
12✔
780
{
781
    if m < 1 || m >= n {
12✔
782
        return Err(InvalidInputError {});
×
783
    }
12✔
784
    let mut rng: Pcg64 = match seed {
12✔
785
        Some(seed) => Pcg64::seed_from_u64(seed),
8✔
786
        None => Pcg64::from_os_rng(),
4✔
787
    };
788
    let mut graph = match initial_graph {
12✔
789
        Some(initial_graph) => initial_graph,
8✔
790
        None => star_graph(
4✔
791
            Some(m),
4✔
792
            None,
4✔
793
            &mut default_node_weight,
4✔
794
            &mut default_edge_weight,
4✔
795
            false,
796
            false,
797
        )?,
×
798
    };
799
    if graph.node_count() < m || graph.node_count() > n {
12✔
800
        return Err(InvalidInputError {});
4✔
801
    }
8✔
802

803
    let mut repeated_nodes: Vec<G::NodeId> = graph
8✔
804
        .node_identifiers()
8✔
805
        .flat_map(|x| {
3,600✔
806
            let degree = graph
3,600✔
807
                .edges_directed(x, Outgoing)
3,600✔
808
                .chain(graph.edges_directed(x, Incoming))
3,600✔
809
                .count();
3,600✔
810
            std::iter::repeat(x).take(degree)
3,600✔
811
        })
3,600✔
812
        .collect();
8✔
813
    let mut source = graph.node_count();
8✔
814
    while source < n {
408✔
815
        let source_index = graph.add_node(default_node_weight());
400✔
816
        let mut targets: IndexSet<G::NodeId, foldhash::fast::RandomState> =
400✔
817
            IndexSet::with_capacity_and_hasher(m, foldhash::fast::RandomState::default());
400✔
818
        while targets.len() < m {
1,112,436✔
819
            targets.insert(*repeated_nodes.choose(&mut rng).unwrap());
1,112,036✔
820
        }
1,112,036✔
821
        for target in &targets {
180,400✔
822
            graph.add_edge(source_index, *target, default_edge_weight());
180,000✔
823
        }
180,000✔
824
        repeated_nodes.extend(targets);
400✔
825
        repeated_nodes.extend(vec![source_index; m]);
400✔
826
        source += 1
400✔
827
    }
828
    Ok(graph)
8✔
829
}
12✔
830

831
/// Generate a random bipartite graph.
832
///
833
/// A bipartite graph is a graph whose nodes can be divided into two disjoint sets,
834
/// informally called "left nodes" and "right nodes", so that every edge connects
835
/// some left node and some right node.
836
///
837
/// Given a number `n` of left nodes, a number `m` of right nodes, and a probability `p`,
838
/// the algorithm creates a graph with `n + m` nodes. For all the `n * m` possible edges,
839
/// each edge is created independently with probability `p`.
840
///
841
/// Arguments:
842
///
843
/// * `num_l_nodes` - The number of "left" nodes in the random bipartite graph.
844
/// * `num_r_nodes` - The number of "right" nodes in the random bipartite graph.
845
/// * `probability` - The probability of creating an edge between two nodes as a float.
846
/// * `seed` - An optional seed to use for the random number generator.
847
/// * `default_node_weight` - A callable that will return the weight to use
848
///   for newly created nodes.
849
/// * `default_edge_weight` - A callable that will return the weight object
850
///   to use for newly created edges.
851
///
852
/// # Example
853
/// ```rust
854
/// use rustworkx_core::petgraph;
855
/// use rustworkx_core::generators::random_bipartite_graph;
856
///
857
/// let g: petgraph::graph::DiGraph<(), ()> = random_bipartite_graph(
858
///     20,
859
///     20,
860
///     1.0,
861
///     None,
862
///     || {()},
863
///     || {()},
864
/// ).unwrap();
865
/// assert_eq!(g.node_count(), 20 + 20);
866
/// assert_eq!(g.edge_count(), 20 * 20);
867
/// ```
868
pub fn random_bipartite_graph<G, T, F, H, M>(
28✔
869
    num_l_nodes: usize,
28✔
870
    num_r_nodes: usize,
28✔
871
    probability: f64,
28✔
872
    seed: Option<u64>,
28✔
873
    mut default_node_weight: F,
28✔
874
    mut default_edge_weight: H,
28✔
875
) -> Result<G, InvalidInputError>
28✔
876
where
28✔
877
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
28✔
878
    F: FnMut() -> T,
28✔
879
    H: FnMut() -> M,
28✔
880
    G::NodeId: Eq + Hash,
28✔
881
{
882
    if num_l_nodes == 0 && num_r_nodes == 0 {
28✔
883
        return Err(InvalidInputError {});
4✔
884
    }
24✔
885
    if !(0.0..=1.0).contains(&probability) {
24✔
886
        return Err(InvalidInputError {});
4✔
887
    }
20✔
888

889
    let mut rng: Pcg64 = match seed {
20✔
890
        Some(seed) => Pcg64::seed_from_u64(seed),
8✔
891
        None => Pcg64::from_os_rng(),
12✔
892
    };
893
    let mut graph = G::with_capacity(num_l_nodes + num_r_nodes, num_l_nodes + num_r_nodes);
20✔
894

895
    for _ in 0..num_l_nodes + num_r_nodes {
260✔
896
        graph.add_node(default_node_weight());
260✔
897
    }
260✔
898

899
    let between = Uniform::new(0.0, 1.0).unwrap();
20✔
900
    for v in 0..num_l_nodes {
140✔
901
        for w in 0..num_r_nodes {
800✔
902
            let random: f64 = between.sample(&mut rng);
800✔
903
            if random < probability {
800✔
904
                graph.add_edge(
388✔
905
                    graph.from_index(v),
388✔
906
                    graph.from_index(num_l_nodes + w),
388✔
907
                    default_edge_weight(),
388✔
908
                );
388✔
909
            }
412✔
910
        }
911
    }
912
    Ok(graph)
20✔
913
}
28✔
914

915
/// Generate a hyperbolic random undirected graph (also called hyperbolic geometric graph).
916
///
917
/// The hyperbolic random graph model connects pairs of nodes with a probability
918
/// that decreases as their hyperbolic distance increases.
919
///
920
/// The number of nodes and the dimension are inferred from the coordinates `pos` of the
921
/// hyperboloid model. The "time" coordinates are inferred from the others, meaning that
922
/// at least 2 coordinates must be provided per node. If `beta` is `None`, all pairs of
923
/// nodes with a distance smaller than ``r`` are connected.
924
///
925
/// Arguments:
926
///
927
/// * `pos` - Hyperboloid model coordinates of the nodes `[p_1, p_2, ...]` where `p_i` is the
928
///   position of node i. The "time" coordinates are inferred.
929
/// * `beta` - Sigmoid sharpness (nonnegative) of the connection probability.
930
/// * `r` - Distance at which the connection probability is 0.5 for the probabilistic model.
931
///   Threshold when `beta` is `None`.
932
/// * `seed` - An optional seed to use for the random number generator.
933
/// * `default_node_weight` - A callable that will return the weight to use
934
///   for newly created nodes.
935
/// * `default_edge_weight` - A callable that will return the weight object
936
///   to use for newly created edges.
937
///
938
/// # Example
939
/// ```rust
940
/// use rustworkx_core::petgraph;
941
/// use rustworkx_core::generators::hyperbolic_random_graph;
942
///
943
/// let g: petgraph::graph::UnGraph<(), ()> = hyperbolic_random_graph(
944
///     &[vec![3_f64.sinh(), 0.],
945
///       vec![-0.5_f64.sinh(), 0.],
946
///       vec![-1_f64.sinh(), 0.]],
947
///     2.,
948
///     None,
949
///     None,
950
///     || {()},
951
///     || {()},
952
/// ).unwrap();
953
/// assert_eq!(g.node_count(), 3);
954
/// assert_eq!(g.edge_count(), 1);
955
/// ```
956
pub fn hyperbolic_random_graph<G, T, F, H, M>(
16✔
957
    pos: &[Vec<f64>],
16✔
958
    r: f64,
16✔
959
    beta: Option<f64>,
16✔
960
    seed: Option<u64>,
16✔
961
    mut default_node_weight: F,
16✔
962
    mut default_edge_weight: H,
16✔
963
) -> Result<G, InvalidInputError>
16✔
964
where
16✔
965
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
16✔
966
    F: FnMut() -> T,
16✔
967
    H: FnMut() -> M,
16✔
968
    G::NodeId: Eq + Hash,
16✔
969
{
970
    let num_nodes = pos.len();
16✔
971
    if num_nodes == 0 {
16✔
972
        return Err(InvalidInputError {});
2✔
973
    }
14✔
974
    if pos
14✔
975
        .iter()
14✔
976
        .any(|xs| xs.iter().any(|x| x.is_nan() || x.is_infinite()))
58✔
977
    {
978
        return Err(InvalidInputError {});
×
979
    }
14✔
980
    let dim = pos[0].len();
14✔
981
    if dim < 2 || pos.iter().any(|x| x.len() != dim) {
28✔
982
        return Err(InvalidInputError {});
2✔
983
    }
12✔
984
    if beta.is_some_and(|b| b < 0. || b.is_nan()) {
12✔
985
        return Err(InvalidInputError {});
2✔
986
    }
10✔
987
    if r < 0. || r.is_nan() {
10✔
988
        return Err(InvalidInputError {});
2✔
989
    }
8✔
990

991
    let mut rng: Pcg64 = match seed {
8✔
992
        Some(seed) => Pcg64::seed_from_u64(seed),
4✔
993
        None => Pcg64::from_os_rng(),
4✔
994
    };
995
    let mut graph = G::with_capacity(num_nodes, num_nodes);
8✔
996
    if graph.is_directed() {
8✔
997
        return Err(InvalidInputError {});
×
998
    }
8✔
999

1000
    for _ in 0..num_nodes {
16✔
1001
        graph.add_node(default_node_weight());
16✔
1002
    }
16✔
1003

1004
    let between = Uniform::new(0.0, 1.0).unwrap();
8✔
1005
    for (v, p1) in pos.iter().enumerate().take(num_nodes - 1) {
8✔
1006
        for (w, p2) in pos.iter().enumerate().skip(v + 1) {
8✔
1007
            let dist = hyperbolic_distance(p1, p2);
8✔
1008
            let is_edge = match beta {
8✔
1009
                Some(b) => {
4✔
1010
                    let prob_inverse = (b / 2. * (dist - r)).exp() + 1.;
4✔
1011
                    let u: f64 = between.sample(&mut rng);
4✔
1012
                    prob_inverse * u < 1.
4✔
1013
                }
1014
                None => dist < r,
4✔
1015
            };
1016
            if is_edge {
8✔
1017
                graph.add_edge(
4✔
1018
                    graph.from_index(v),
4✔
1019
                    graph.from_index(w),
4✔
1020
                    default_edge_weight(),
4✔
1021
                );
4✔
1022
            }
4✔
1023
        }
1024
    }
1025
    Ok(graph)
8✔
1026
}
16✔
1027

1028
#[inline]
1029
fn hyperbolic_distance(x: &[f64], y: &[f64]) -> f64 {
8✔
1030
    let mut sum_squared_x = 0.;
8✔
1031
    let mut sum_squared_y = 0.;
8✔
1032
    let mut inner_product = 0.;
8✔
1033
    for (x_i, y_i) in x.iter().zip(y.iter()) {
16✔
1034
        if x_i.is_infinite() || y_i.is_infinite() || x_i.is_nan() || y_i.is_nan() {
16✔
1035
            return f64::NAN;
×
1036
        }
16✔
1037
        sum_squared_x = x_i.mul_add(*x_i, sum_squared_x);
16✔
1038
        sum_squared_y = y_i.mul_add(*y_i, sum_squared_y);
16✔
1039
        inner_product = x_i.mul_add(*y_i, inner_product);
16✔
1040
    }
1041
    let arg = (1. + sum_squared_x).sqrt() * (1. + sum_squared_y).sqrt() - inner_product;
8✔
1042
    if arg < 1. {
8✔
1043
        0.
×
1044
    } else {
1045
        arg.acosh()
8✔
1046
    }
1047
}
8✔
1048

1049
#[cfg(test)]
1050
mod tests {
1051
    use crate::generators::InvalidInputError;
1052
    use crate::generators::{
1053
        barabasi_albert_graph, gnm_random_graph, gnp_random_graph, hyperbolic_random_graph,
1054
        path_graph, random_bipartite_graph, random_geometric_graph, random_regular_graph,
1055
        sbm_random_graph,
1056
    };
1057
    use crate::petgraph;
1058

1059
    use super::hyperbolic_distance;
1060

1061
    // Test gnp_random_graph
1062

1063
    #[test]
1064
    fn test_gnp_random_graph_directed() {
1065
        let g: petgraph::graph::DiGraph<(), ()> =
1066
            gnp_random_graph(20, 0.5, Some(10), || (), || ()).unwrap();
1067
        assert_eq!(g.node_count(), 20);
1068
        assert_eq!(g.edge_count(), 189);
1069
    }
1070

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

1082
    #[test]
1083
    fn test_gnp_random_graph_directed_empty() {
1084
        let g: petgraph::graph::DiGraph<(), ()> =
1085
            gnp_random_graph(20, 0.0, None, || (), || ()).unwrap();
1086
        assert_eq!(g.node_count(), 20);
1087
        assert_eq!(g.edge_count(), 0);
1088
    }
1089

1090
    #[test]
1091
    fn test_gnp_random_graph_directed_complete() {
1092
        let g: petgraph::graph::DiGraph<(), ()> =
1093
            gnp_random_graph(20, 1.0, None, || (), || ()).unwrap();
1094
        assert_eq!(g.node_count(), 20);
1095
        assert_eq!(g.edge_count(), 20 * (20 - 1));
1096
    }
1097

1098
    #[test]
1099
    fn test_gnp_random_graph_undirected() {
1100
        let g: petgraph::graph::UnGraph<(), ()> =
1101
            gnp_random_graph(20, 0.5, Some(10), || (), || ()).unwrap();
1102
        assert_eq!(g.node_count(), 20);
1103
        assert_eq!(g.edge_count(), 105);
1104
    }
1105

1106
    #[test]
1107
    fn test_gnp_random_graph_undirected_empty() {
1108
        let g: petgraph::graph::UnGraph<(), ()> =
1109
            gnp_random_graph(20, 0.0, None, || (), || ()).unwrap();
1110
        assert_eq!(g.node_count(), 20);
1111
        assert_eq!(g.edge_count(), 0);
1112
    }
1113

1114
    #[test]
1115
    fn test_gnp_random_graph_undirected_complete() {
1116
        let g: petgraph::graph::UnGraph<(), ()> =
1117
            gnp_random_graph(20, 1.0, None, || (), || ()).unwrap();
1118
        assert_eq!(g.node_count(), 20);
1119
        assert_eq!(g.edge_count(), 20 * (20 - 1) / 2);
1120
    }
1121

1122
    #[test]
1123
    fn test_gnp_random_graph_error() {
1124
        match gnp_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1125
            0,
1126
            3.0,
1127
            None,
1128
            || (),
1129
            || (),
1130
        ) {
1131
            Ok(_) => panic!("Returned a non-error"),
1132
            Err(e) => assert_eq!(e, InvalidInputError),
1133
        };
1134
    }
1135

1136
    // Test gnm_random_graph
1137

1138
    #[test]
1139
    fn test_gnm_random_graph_directed() {
1140
        let g: petgraph::graph::DiGraph<(), ()> =
1141
            gnm_random_graph(20, 100, None, || (), || ()).unwrap();
1142
        assert_eq!(g.node_count(), 20);
1143
        assert_eq!(g.edge_count(), 100);
1144
    }
1145

1146
    #[test]
1147
    fn test_gnm_random_graph_directed_empty() {
1148
        let g: petgraph::graph::DiGraph<(), ()> =
1149
            gnm_random_graph(20, 0, None, || (), || ()).unwrap();
1150
        assert_eq!(g.node_count(), 20);
1151
        assert_eq!(g.edge_count(), 0);
1152
    }
1153

1154
    #[test]
1155
    fn test_gnm_random_graph_directed_complete() {
1156
        let g: petgraph::graph::DiGraph<(), ()> =
1157
            gnm_random_graph(20, 20 * (20 - 1), None, || (), || ()).unwrap();
1158
        assert_eq!(g.node_count(), 20);
1159
        assert_eq!(g.edge_count(), 20 * (20 - 1));
1160
    }
1161

1162
    #[test]
1163
    fn test_gnm_random_graph_directed_max_edges() {
1164
        let n = 20;
1165
        let max_m = n * (n - 1);
1166
        let g: petgraph::graph::DiGraph<(), ()> =
1167
            gnm_random_graph(n, max_m, None, || (), || ()).unwrap();
1168
        assert_eq!(g.node_count(), n);
1169
        assert_eq!(g.edge_count(), max_m);
1170
        // passing the max edges for the passed number of nodes
1171
        let g: petgraph::graph::DiGraph<(), ()> =
1172
            gnm_random_graph(n, max_m + 1, None, || (), || ()).unwrap();
1173
        assert_eq!(g.node_count(), n);
1174
        assert_eq!(g.edge_count(), max_m);
1175
        // passing a seed when passing max edges has no effect
1176
        let g: petgraph::graph::DiGraph<(), ()> =
1177
            gnm_random_graph(n, max_m, Some(55), || (), || ()).unwrap();
1178
        assert_eq!(g.node_count(), n);
1179
        assert_eq!(g.edge_count(), max_m);
1180
    }
1181

1182
    #[test]
1183
    fn test_gnm_random_graph_error() {
1184
        match gnm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1185
            0,
1186
            0,
1187
            None,
1188
            || (),
1189
            || (),
1190
        ) {
1191
            Ok(_) => panic!("Returned a non-error"),
1192
            Err(e) => assert_eq!(e, InvalidInputError),
1193
        };
1194
    }
1195

1196
    // Test sbm_random_graph
1197
    #[test]
1198
    fn test_sbm_directed_complete_blocks_loops() {
1199
        let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1200
            &[1, 2],
1201
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1202
            true,
1203
            Some(10),
1204
            || (),
1205
            || (),
1206
        )
1207
        .unwrap();
1208
        assert_eq!(g.node_count(), 3);
1209
        assert_eq!(g.edge_count(), 6);
1210
        for (u, v) in [(1, 1), (1, 2), (2, 1), (2, 2), (0, 1), (0, 2)] {
1211
            assert!(g.contains_edge(u.into(), v.into()));
1212
        }
1213
        assert!(!g.contains_edge(1.into(), 0.into()));
1214
        assert!(!g.contains_edge(2.into(), 0.into()));
1215
    }
1216

1217
    #[test]
1218
    fn test_sbm_undirected_complete_blocks_loops() {
1219
        let g = sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1220
            &[1, 2],
1221
            &ndarray::arr2(&[[0., 1.], [1., 1.]]).view(),
1222
            true,
1223
            Some(10),
1224
            || (),
1225
            || (),
1226
        )
1227
        .unwrap();
1228
        assert_eq!(g.node_count(), 3);
1229
        assert_eq!(g.edge_count(), 5);
1230
        for (u, v) in [(1, 1), (1, 2), (2, 2), (0, 1), (0, 2)] {
1231
            assert!(g.contains_edge(u.into(), v.into()));
1232
        }
1233
        assert!(!g.contains_edge(0.into(), 0.into()));
1234
    }
1235

1236
    #[test]
1237
    fn test_sbm_directed_complete_blocks_noloops() {
1238
        let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1239
            &[1, 2],
1240
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1241
            false,
1242
            Some(10),
1243
            || (),
1244
            || (),
1245
        )
1246
        .unwrap();
1247
        assert_eq!(g.node_count(), 3);
1248
        assert_eq!(g.edge_count(), 4);
1249
        for (u, v) in [(1, 2), (2, 1), (0, 1), (0, 2)] {
1250
            assert!(g.contains_edge(u.into(), v.into()));
1251
        }
1252
        assert!(!g.contains_edge(1.into(), 0.into()));
1253
        assert!(!g.contains_edge(2.into(), 0.into()));
1254
        for u in 0..2 {
1255
            assert!(!g.contains_edge(u.into(), u.into()));
1256
        }
1257
    }
1258

1259
    #[test]
1260
    fn test_sbm_undirected_complete_blocks_noloops() {
1261
        let g = sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1262
            &[1, 2],
1263
            &ndarray::arr2(&[[0., 1.], [1., 1.]]).view(),
1264
            false,
1265
            Some(10),
1266
            || (),
1267
            || (),
1268
        )
1269
        .unwrap();
1270
        assert_eq!(g.node_count(), 3);
1271
        assert_eq!(g.edge_count(), 3);
1272
        for (u, v) in [(1, 2), (0, 1), (0, 2)] {
1273
            assert!(g.contains_edge(u.into(), v.into()));
1274
        }
1275
        for u in 0..2 {
1276
            assert!(!g.contains_edge(u.into(), u.into()));
1277
        }
1278
    }
1279

1280
    #[test]
1281
    fn test_sbm_bad_array_rows_error() {
1282
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1283
            &[1, 2],
1284
            &ndarray::arr2(&[[0., 1.], [1., 1.], [1., 1.]]).view(),
1285
            true,
1286
            Some(10),
1287
            || (),
1288
            || (),
1289
        ) {
1290
            Ok(_) => panic!("Returned a non-error"),
1291
            Err(e) => assert_eq!(e, InvalidInputError),
1292
        };
1293
    }
1294
    #[test]
1295

1296
    fn test_sbm_bad_array_cols_error() {
1297
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1298
            &[1, 2],
1299
            &ndarray::arr2(&[[0., 1., 1.], [1., 1., 1.]]).view(),
1300
            true,
1301
            Some(10),
1302
            || (),
1303
            || (),
1304
        ) {
1305
            Ok(_) => panic!("Returned a non-error"),
1306
            Err(e) => assert_eq!(e, InvalidInputError),
1307
        };
1308
    }
1309

1310
    #[test]
1311
    fn test_sbm_asymmetric_array_error() {
1312
        match sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1313
            &[1, 2],
1314
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1315
            true,
1316
            Some(10),
1317
            || (),
1318
            || (),
1319
        ) {
1320
            Ok(_) => panic!("Returned a non-error"),
1321
            Err(e) => assert_eq!(e, InvalidInputError),
1322
        };
1323
    }
1324

1325
    #[test]
1326
    fn test_sbm_invalid_probability_error() {
1327
        match sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1328
            &[1, 2],
1329
            &ndarray::arr2(&[[0., 1.], [0., -1.]]).view(),
1330
            true,
1331
            Some(10),
1332
            || (),
1333
            || (),
1334
        ) {
1335
            Ok(_) => panic!("Returned a non-error"),
1336
            Err(e) => assert_eq!(e, InvalidInputError),
1337
        };
1338
    }
1339

1340
    #[test]
1341
    fn test_sbm_empty_error() {
1342
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1343
            &[],
1344
            &ndarray::arr2(&[[]]).view(),
1345
            true,
1346
            Some(10),
1347
            || (),
1348
            || (),
1349
        ) {
1350
            Ok(_) => panic!("Returned a non-error"),
1351
            Err(e) => assert_eq!(e, InvalidInputError),
1352
        };
1353
    }
1354

1355
    // Test random_geometric_graph
1356

1357
    #[test]
1358
    fn test_random_geometric_empty() {
1359
        let g: petgraph::graph::UnGraph<Vec<f64>, ()> =
1360
            random_geometric_graph(20, 0.0, 2, None, 2.0, None, || ()).unwrap();
1361
        assert_eq!(g.node_count(), 20);
1362
        assert_eq!(g.edge_count(), 0);
1363
    }
1364

1365
    #[test]
1366
    fn test_random_geometric_complete() {
1367
        let g: petgraph::graph::UnGraph<Vec<f64>, ()> =
1368
            random_geometric_graph(10, 1.42, 2, None, 2.0, None, || ()).unwrap();
1369
        assert_eq!(g.node_count(), 10);
1370
        assert_eq!(g.edge_count(), 45);
1371
    }
1372

1373
    #[test]
1374
    fn test_random_geometric_bad_num_nodes() {
1375
        match random_geometric_graph::<petgraph::graph::UnGraph<Vec<f64>, ()>, _, ()>(
1376
            0,
1377
            1.0,
1378
            2,
1379
            None,
1380
            2.0,
1381
            None,
1382
            || (),
1383
        ) {
1384
            Ok(_) => panic!("Returned a non-error"),
1385
            Err(e) => assert_eq!(e, InvalidInputError),
1386
        };
1387
    }
1388

1389
    #[test]
1390
    fn test_random_geometric_bad_pos() {
1391
        match random_geometric_graph::<petgraph::graph::UnGraph<Vec<f64>, ()>, _, ()>(
1392
            3,
1393
            0.15,
1394
            3,
1395
            Some(vec![vec![0.5, 0.5]]),
1396
            2.0,
1397
            None,
1398
            || (),
1399
        ) {
1400
            Ok(_) => panic!("Returned a non-error"),
1401
            Err(e) => assert_eq!(e, InvalidInputError),
1402
        };
1403
    }
1404

1405
    #[test]
1406
    fn test_barabasi_albert_graph_seeding() {
1407
        use petgraph::visit::EdgeRef;
1408
        let g1: petgraph::graph::UnGraph<(), ()> =
1409
            barabasi_albert_graph(7, 2, Some(42), None, || (), || ()).unwrap();
1410
        let g2: petgraph::graph::UnGraph<(), ()> =
1411
            barabasi_albert_graph(7, 2, Some(42), None, || (), || ()).unwrap();
1412
        // Same seeds should yield the same graph
1413
        let edges1: std::collections::BTreeSet<_> = g1
1414
            .edge_references()
1415
            .map(|e| (e.source(), e.target()))
1416
            .collect();
1417
        let edges2: std::collections::BTreeSet<_> = g2
1418
            .edge_references()
1419
            .map(|e| (e.source(), e.target()))
1420
            .collect();
1421
        for (e1, e2) in edges1.iter().zip(edges2.iter()) {
1422
            assert_eq!(e1, e2);
1423
        }
1424
    }
1425

1426
    #[test]
1427
    fn test_barabasi_albert_graph_starting_graph() {
1428
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1429
            path_graph(Some(40), None, || (), || (), false).unwrap();
1430
        let graph =
1431
            barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()).unwrap();
1432
        assert_eq!(graph.node_count(), 500);
1433
        assert_eq!(graph.edge_count(), 18439);
1434
    }
1435

1436
    #[test]
1437
    fn test_barabasi_albert_graph_invalid_starting_size() {
1438
        match barabasi_albert_graph(
1439
            5,
1440
            40,
1441
            None,
1442
            None::<petgraph::graph::UnGraph<(), ()>>,
1443
            || (),
1444
            || (),
1445
        ) {
1446
            Ok(_) => panic!("Returned a non-error"),
1447
            Err(e) => assert_eq!(e, InvalidInputError),
1448
        }
1449
    }
1450

1451
    #[test]
1452
    fn test_barabasi_albert_graph_invalid_equal_starting_size() {
1453
        match barabasi_albert_graph(
1454
            5,
1455
            5,
1456
            None,
1457
            None::<petgraph::graph::UnGraph<(), ()>>,
1458
            || (),
1459
            || (),
1460
        ) {
1461
            Ok(_) => panic!("Returned a non-error"),
1462
            Err(e) => assert_eq!(e, InvalidInputError),
1463
        }
1464
    }
1465

1466
    #[test]
1467
    fn test_barabasi_albert_graph_invalid_starting_graph() {
1468
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1469
            path_graph(Some(4), None, || (), || (), false).unwrap();
1470
        match barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()) {
1471
            Ok(_) => panic!("Returned a non-error"),
1472
            Err(e) => assert_eq!(e, InvalidInputError),
1473
        }
1474
    }
1475

1476
    // Test random_bipartite_graph
1477

1478
    #[test]
1479
    fn test_random_bipartite_graph_directed() {
1480
        let g: petgraph::graph::DiGraph<(), ()> =
1481
            random_bipartite_graph(10, 10, 0.5, Some(10), || (), || ()).unwrap();
1482
        assert_eq!(g.node_count(), 20);
1483
        assert_eq!(g.edge_count(), 57);
1484
    }
1485

1486
    #[test]
1487
    fn test_random_bipartite_graph_directed_empty() {
1488
        let g: petgraph::graph::DiGraph<(), ()> =
1489
            random_bipartite_graph(5, 10, 0.0, None, || (), || ()).unwrap();
1490
        assert_eq!(g.node_count(), 15);
1491
        assert_eq!(g.edge_count(), 0);
1492
    }
1493

1494
    #[test]
1495
    fn test_random_bipartite_graph_directed_complete() {
1496
        let g: petgraph::graph::DiGraph<(), ()> =
1497
            random_bipartite_graph(10, 5, 1.0, None, || (), || ()).unwrap();
1498
        assert_eq!(g.node_count(), 15);
1499
        assert_eq!(g.edge_count(), 10 * 5);
1500
    }
1501

1502
    #[test]
1503
    fn test_random_bipartite_graph_undirected() {
1504
        let g: petgraph::graph::UnGraph<(), ()> =
1505
            random_bipartite_graph(10, 10, 0.5, Some(10), || (), || ()).unwrap();
1506
        assert_eq!(g.node_count(), 20);
1507
        assert_eq!(g.edge_count(), 57);
1508
    }
1509

1510
    #[test]
1511
    fn test_random_bipartite_graph_undirected_empty() {
1512
        let g: petgraph::graph::UnGraph<(), ()> =
1513
            random_bipartite_graph(5, 10, 0.0, None, || (), || ()).unwrap();
1514
        assert_eq!(g.node_count(), 15);
1515
        assert_eq!(g.edge_count(), 0);
1516
    }
1517

1518
    #[test]
1519
    fn test_random_bipartite_graph_undirected_complete() {
1520
        let g: petgraph::graph::UnGraph<(), ()> =
1521
            random_bipartite_graph(10, 5, 1.0, None, || (), || ()).unwrap();
1522
        assert_eq!(g.node_count(), 15);
1523
        assert_eq!(g.edge_count(), 10 * 5);
1524
    }
1525

1526
    #[test]
1527
    fn test_random_bipartite_graph_error() {
1528
        match random_bipartite_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1529
            0,
1530
            0,
1531
            3.0,
1532
            None,
1533
            || (),
1534
            || (),
1535
        ) {
1536
            Ok(_) => panic!("Returned a non-error"),
1537
            Err(e) => assert_eq!(e, InvalidInputError),
1538
        };
1539
    }
1540

1541
    // Test hyperbolic_random_graph
1542
    //
1543
    // Hyperboloid (H^2) "polar" coordinates (r, theta) are transformed to "cartesian"
1544
    // coordinates using
1545
    // z = cosh(r)
1546
    // x = sinh(r)cos(theta)
1547
    // y = sinh(r)sin(theta)
1548

1549
    #[test]
1550
    fn test_hyperbolic_dist() {
1551
        assert_eq!(
1552
            hyperbolic_distance(&[3_f64.sinh(), 0.], &[-0.5_f64.sinh(), 0.]),
1553
            3.5
1554
        );
1555
    }
1556
    #[test]
1557
    fn test_hyperbolic_dist_inf() {
1558
        assert!(hyperbolic_distance(&[f64::INFINITY, 0.], &[0., 0.]).is_nan());
1559
    }
1560

1561
    #[test]
1562
    fn test_hyperbolic_random_graph_seeded() {
1563
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1564
            &[
1565
                vec![3_f64.sinh(), 0.],
1566
                vec![-0.5_f64.sinh(), 0.],
1567
                vec![0.5_f64.sinh(), 0.],
1568
                vec![0., 0.],
1569
            ],
1570
            0.75,
1571
            Some(10000.),
1572
            Some(10),
1573
            || (),
1574
            || (),
1575
        )
1576
        .unwrap();
1577
        assert_eq!(g.node_count(), 4);
1578
        assert_eq!(g.edge_count(), 2);
1579
    }
1580

1581
    #[test]
1582
    fn test_hyperbolic_random_graph_threshold() {
1583
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1584
            &[
1585
                vec![3_f64.sinh(), 0.],
1586
                vec![-0.5_f64.sinh(), 0.],
1587
                vec![-1_f64.sinh(), 0.],
1588
            ],
1589
            1.,
1590
            None,
1591
            None,
1592
            || (),
1593
            || (),
1594
        )
1595
        .unwrap();
1596
        assert_eq!(g.node_count(), 3);
1597
        assert_eq!(g.edge_count(), 1);
1598
    }
1599

1600
    #[test]
1601
    fn test_hyperbolic_random_graph_invalid_dim_error() {
1602
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1603
            &[vec![0.]],
1604
            1.,
1605
            None,
1606
            None,
1607
            || (),
1608
            || (),
1609
        ) {
1610
            Ok(_) => panic!("Returned a non-error"),
1611
            Err(e) => assert_eq!(e, InvalidInputError),
1612
        }
1613
    }
1614

1615
    #[test]
1616
    fn test_hyperbolic_random_graph_neg_r_error() {
1617
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1618
            &[vec![0., 0.], vec![0., 0.]],
1619
            -1.,
1620
            None,
1621
            None,
1622
            || (),
1623
            || (),
1624
        ) {
1625
            Ok(_) => panic!("Returned a non-error"),
1626
            Err(e) => assert_eq!(e, InvalidInputError),
1627
        }
1628
    }
1629

1630
    #[test]
1631
    fn test_hyperbolic_random_graph_neg_beta_error() {
1632
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1633
            &[vec![0., 0.], vec![0., 0.]],
1634
            1.,
1635
            Some(-1.),
1636
            None,
1637
            || (),
1638
            || (),
1639
        ) {
1640
            Ok(_) => panic!("Returned a non-error"),
1641
            Err(e) => assert_eq!(e, InvalidInputError),
1642
        }
1643
    }
1644

1645
    #[test]
1646
    fn test_hyperbolic_random_graph_diff_dims_error() {
1647
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1648
            &[vec![0., 0.], vec![0., 0., 0.]],
1649
            1.,
1650
            None,
1651
            None,
1652
            || (),
1653
            || (),
1654
        ) {
1655
            Ok(_) => panic!("Returned a non-error"),
1656
            Err(e) => assert_eq!(e, InvalidInputError),
1657
        }
1658
    }
1659

1660
    #[test]
1661
    fn test_hyperbolic_random_graph_empty_error() {
1662
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1663
            &[],
1664
            1.,
1665
            None,
1666
            None,
1667
            || (),
1668
            || (),
1669
        ) {
1670
            Ok(_) => panic!("Returned a non-error"),
1671
            Err(e) => assert_eq!(e, InvalidInputError),
1672
        }
1673
    }
1674

1675
    #[test]
1676
    fn test_hyperbolic_random_graph_directed_error() {
1677
        match hyperbolic_random_graph::<petgraph::graph::DiGraph<(), ()>, _, _, _, _>(
1678
            &[vec![0., 0.], vec![0., 0.]],
1679
            1.,
1680
            None,
1681
            None,
1682
            || (),
1683
            || (),
1684
        ) {
1685
            Ok(_) => panic!("Returned a non-error"),
1686
            Err(e) => assert_eq!(e, InvalidInputError),
1687
        }
1688
    }
1689
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc