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

Qiskit / rustworkx / 15333628866

29 May 2025 08:51PM UTC coverage: 95.236% (-0.01%) from 95.247%
15333628866

push

github

web-flow
Added property checks for lollipop graph (#1453)

* Added property checks for lollipop graph

Added a test using QuickCheck to verify that the lollipop_graph generator produces graphs with the correct number of nodes and edges based on the input sizes of the mesh and path.

Updated the Cargo.toml to add dependecies and test target.

* Update based on comment

* Updates based on comments

* Lint issue fixed

* Update main.rs

---------

Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>

18733 of 19670 relevant lines covered (95.24%)

1080245.76 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,552✔
303
                    let random: f64 = between.sample(&mut rng);
10,506✔
304
                    let lr: f64 = (1.0 - random).ln();
10,506✔
305
                    w = w + 1 + ((lr / lp) as isize);
10,506✔
306
                    while w >= v && v < num_nodes {
11,144✔
307
                        w -= v;
638✔
308
                        v += 1;
638✔
309
                    }
638✔
310
                    // Skip self-loops
311
                    if v == w {
10,506✔
312
                        w -= v;
×
313
                        v += 1;
×
314
                    }
10,506✔
315
                    if v < num_nodes {
10,506✔
316
                        let v_index = graph.from_index(v as usize);
10,460✔
317
                        let w_index = graph.from_index(w as usize);
10,460✔
318
                        graph.add_edge(w_index, v_index, default_edge_weight());
10,460✔
319
                    }
10,460✔
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,226✔
419
    where
4,226✔
420
        G: GraphBase + NodeIndexable,
4,226✔
421
        for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
4,226✔
422
    {
4,226✔
423
        let mut found = false;
4,226✔
424
        for edge in graph.edge_references() {
1,147,452✔
425
            if graph.to_index(edge.source()) == source && graph.to_index(edge.target()) == target {
1,147,452✔
426
                found = true;
338✔
427
                break;
338✔
428
            }
1,147,114✔
429
        }
430
        found
4,226✔
431
    }
4,226✔
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,400✔
462
            let u = between.sample(&mut rng);
4,360✔
463
            let v = between.sample(&mut rng);
4,360✔
464
            let u_index = graph.from_index(u);
4,360✔
465
            let v_index = graph.from_index(v);
4,360✔
466
            // avoid self-loops and multi-graphs
4,360✔
467
            if u != v && !find_edge(&graph, u, v) {
4,360✔
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,101,440✔
819
            targets.insert(*repeated_nodes.choose(&mut rng).unwrap());
1,101,040✔
820
        }
1,101,040✔
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