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

Qiskit / rustworkx / 15890388565

26 Jun 2025 12:37AM UTC coverage: 95.201% (-0.01%) from 95.211%
15890388565

push

github

web-flow
Adding Quickcheck for binomial tree graph. (#1470)

The quickcheck verifies the structure of binomial tree graph by checking number of node, number of edges and if they are bidirectional.

I plan on adding quickchecks for all the deterministic graph generators and some other algos like bipartite colouring before moving forward.

19383 of 20360 relevant lines covered (95.2%)

1044261.35 hits per line

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

70.57
/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 hashbrown::HashSet;
28
use rand::prelude::*;
29
use rand_distr::{Distribution, Uniform};
30
use rand_pcg::Pcg64;
31

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

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 function
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_os_rng(),
×
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

8✔
804
    let mut repeated_nodes: Vec<G::NodeId> = graph
8✔
805
        .node_identifiers()
8✔
806
        .flat_map(|x| {
3,600✔
807
            let degree = graph
3,600✔
808
                .edges_directed(x, Outgoing)
3,600✔
809
                .chain(graph.edges_directed(x, Incoming))
3,600✔
810
                .count();
3,600✔
811
            std::iter::repeat(x).take(degree)
3,600✔
812
        })
3,600✔
813
        .collect();
8✔
814
    let mut source = graph.node_count();
8✔
815
    while source < n {
408✔
816
        let source_index = graph.add_node(default_node_weight());
400✔
817
        let mut targets: HashSet<G::NodeId> = HashSet::with_capacity(m);
400✔
818
        while targets.len() < m {
1,102,216✔
819
            targets.insert(*repeated_nodes.choose(&mut rng).unwrap());
1,101,816✔
820
        }
1,101,816✔
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
{
28✔
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

20✔
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
{
16✔
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

8✔
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_starting_graph() {
1407
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1408
            path_graph(Some(40), None, || (), || (), false).unwrap();
1409
        let graph =
1410
            barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()).unwrap();
1411
        assert_eq!(graph.node_count(), 500);
1412
        assert_eq!(graph.edge_count(), 18439);
1413
    }
1414

1415
    #[test]
1416
    fn test_barabasi_albert_graph_invalid_starting_size() {
1417
        match barabasi_albert_graph(
1418
            5,
1419
            40,
1420
            None,
1421
            None::<petgraph::graph::UnGraph<(), ()>>,
1422
            || (),
1423
            || (),
1424
        ) {
1425
            Ok(_) => panic!("Returned a non-error"),
1426
            Err(e) => assert_eq!(e, InvalidInputError),
1427
        }
1428
    }
1429

1430
    #[test]
1431
    fn test_barabasi_albert_graph_invalid_equal_starting_size() {
1432
        match barabasi_albert_graph(
1433
            5,
1434
            5,
1435
            None,
1436
            None::<petgraph::graph::UnGraph<(), ()>>,
1437
            || (),
1438
            || (),
1439
        ) {
1440
            Ok(_) => panic!("Returned a non-error"),
1441
            Err(e) => assert_eq!(e, InvalidInputError),
1442
        }
1443
    }
1444

1445
    #[test]
1446
    fn test_barabasi_albert_graph_invalid_starting_graph() {
1447
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1448
            path_graph(Some(4), None, || (), || (), false).unwrap();
1449
        match barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()) {
1450
            Ok(_) => panic!("Returned a non-error"),
1451
            Err(e) => assert_eq!(e, InvalidInputError),
1452
        }
1453
    }
1454

1455
    // Test random_bipartite_graph
1456

1457
    #[test]
1458
    fn test_random_bipartite_graph_directed() {
1459
        let g: petgraph::graph::DiGraph<(), ()> =
1460
            random_bipartite_graph(10, 10, 0.5, Some(10), || (), || ()).unwrap();
1461
        assert_eq!(g.node_count(), 20);
1462
        assert_eq!(g.edge_count(), 57);
1463
    }
1464

1465
    #[test]
1466
    fn test_random_bipartite_graph_directed_empty() {
1467
        let g: petgraph::graph::DiGraph<(), ()> =
1468
            random_bipartite_graph(5, 10, 0.0, None, || (), || ()).unwrap();
1469
        assert_eq!(g.node_count(), 15);
1470
        assert_eq!(g.edge_count(), 0);
1471
    }
1472

1473
    #[test]
1474
    fn test_random_bipartite_graph_directed_complete() {
1475
        let g: petgraph::graph::DiGraph<(), ()> =
1476
            random_bipartite_graph(10, 5, 1.0, None, || (), || ()).unwrap();
1477
        assert_eq!(g.node_count(), 15);
1478
        assert_eq!(g.edge_count(), 10 * 5);
1479
    }
1480

1481
    #[test]
1482
    fn test_random_bipartite_graph_undirected() {
1483
        let g: petgraph::graph::UnGraph<(), ()> =
1484
            random_bipartite_graph(10, 10, 0.5, Some(10), || (), || ()).unwrap();
1485
        assert_eq!(g.node_count(), 20);
1486
        assert_eq!(g.edge_count(), 57);
1487
    }
1488

1489
    #[test]
1490
    fn test_random_bipartite_graph_undirected_empty() {
1491
        let g: petgraph::graph::UnGraph<(), ()> =
1492
            random_bipartite_graph(5, 10, 0.0, None, || (), || ()).unwrap();
1493
        assert_eq!(g.node_count(), 15);
1494
        assert_eq!(g.edge_count(), 0);
1495
    }
1496

1497
    #[test]
1498
    fn test_random_bipartite_graph_undirected_complete() {
1499
        let g: petgraph::graph::UnGraph<(), ()> =
1500
            random_bipartite_graph(10, 5, 1.0, None, || (), || ()).unwrap();
1501
        assert_eq!(g.node_count(), 15);
1502
        assert_eq!(g.edge_count(), 10 * 5);
1503
    }
1504

1505
    #[test]
1506
    fn test_random_bipartite_graph_error() {
1507
        match random_bipartite_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1508
            0,
1509
            0,
1510
            3.0,
1511
            None,
1512
            || (),
1513
            || (),
1514
        ) {
1515
            Ok(_) => panic!("Returned a non-error"),
1516
            Err(e) => assert_eq!(e, InvalidInputError),
1517
        };
1518
    }
1519

1520
    // Test hyperbolic_random_graph
1521
    //
1522
    // Hyperboloid (H^2) "polar" coordinates (r, theta) are transformed to "cartesian"
1523
    // coordinates using
1524
    // z = cosh(r)
1525
    // x = sinh(r)cos(theta)
1526
    // y = sinh(r)sin(theta)
1527

1528
    #[test]
1529
    fn test_hyperbolic_dist() {
1530
        assert_eq!(
1531
            hyperbolic_distance(&[3_f64.sinh(), 0.], &[-0.5_f64.sinh(), 0.]),
1532
            3.5
1533
        );
1534
    }
1535
    #[test]
1536
    fn test_hyperbolic_dist_inf() {
1537
        assert!(hyperbolic_distance(&[f64::INFINITY, 0.], &[0., 0.]).is_nan());
1538
    }
1539

1540
    #[test]
1541
    fn test_hyperbolic_random_graph_seeded() {
1542
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1543
            &[
1544
                vec![3_f64.sinh(), 0.],
1545
                vec![-0.5_f64.sinh(), 0.],
1546
                vec![0.5_f64.sinh(), 0.],
1547
                vec![0., 0.],
1548
            ],
1549
            0.75,
1550
            Some(10000.),
1551
            Some(10),
1552
            || (),
1553
            || (),
1554
        )
1555
        .unwrap();
1556
        assert_eq!(g.node_count(), 4);
1557
        assert_eq!(g.edge_count(), 2);
1558
    }
1559

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

1579
    #[test]
1580
    fn test_hyperbolic_random_graph_invalid_dim_error() {
1581
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1582
            &[vec![0.]],
1583
            1.,
1584
            None,
1585
            None,
1586
            || (),
1587
            || (),
1588
        ) {
1589
            Ok(_) => panic!("Returned a non-error"),
1590
            Err(e) => assert_eq!(e, InvalidInputError),
1591
        }
1592
    }
1593

1594
    #[test]
1595
    fn test_hyperbolic_random_graph_neg_r_error() {
1596
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1597
            &[vec![0., 0.], vec![0., 0.]],
1598
            -1.,
1599
            None,
1600
            None,
1601
            || (),
1602
            || (),
1603
        ) {
1604
            Ok(_) => panic!("Returned a non-error"),
1605
            Err(e) => assert_eq!(e, InvalidInputError),
1606
        }
1607
    }
1608

1609
    #[test]
1610
    fn test_hyperbolic_random_graph_neg_beta_error() {
1611
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1612
            &[vec![0., 0.], vec![0., 0.]],
1613
            1.,
1614
            Some(-1.),
1615
            None,
1616
            || (),
1617
            || (),
1618
        ) {
1619
            Ok(_) => panic!("Returned a non-error"),
1620
            Err(e) => assert_eq!(e, InvalidInputError),
1621
        }
1622
    }
1623

1624
    #[test]
1625
    fn test_hyperbolic_random_graph_diff_dims_error() {
1626
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1627
            &[vec![0., 0.], vec![0., 0., 0.]],
1628
            1.,
1629
            None,
1630
            None,
1631
            || (),
1632
            || (),
1633
        ) {
1634
            Ok(_) => panic!("Returned a non-error"),
1635
            Err(e) => assert_eq!(e, InvalidInputError),
1636
        }
1637
    }
1638

1639
    #[test]
1640
    fn test_hyperbolic_random_graph_empty_error() {
1641
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1642
            &[],
1643
            1.,
1644
            None,
1645
            None,
1646
            || (),
1647
            || (),
1648
        ) {
1649
            Ok(_) => panic!("Returned a non-error"),
1650
            Err(e) => assert_eq!(e, InvalidInputError),
1651
        }
1652
    }
1653

1654
    #[test]
1655
    fn test_hyperbolic_random_graph_directed_error() {
1656
        match hyperbolic_random_graph::<petgraph::graph::DiGraph<(), ()>, _, _, _, _>(
1657
            &[vec![0., 0.], vec![0., 0.]],
1658
            1.,
1659
            None,
1660
            None,
1661
            || (),
1662
            || (),
1663
        ) {
1664
            Ok(_) => panic!("Returned a non-error"),
1665
            Err(e) => assert_eq!(e, InvalidInputError),
1666
        }
1667
    }
1668
}
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