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

Qiskit / rustworkx / 21461907802

29 Jan 2026 01:20AM UTC coverage: 94.203% (-0.01%) from 94.214%
21461907802

push

github

web-flow
Bump fonttools from 4.57.0 to 4.60.2 (#1541)

Bumps [fonttools](https://github.com/fonttools/fonttools) from 4.57.0 to 4.60.2.
- [Release notes](https://github.com/fonttools/fonttools/releases)
- [Changelog](https://github.com/fonttools/fonttools/blob/main/NEWS.rst)
- [Commits](https://github.com/fonttools/fonttools/compare/4.57.0...4.60.2)

---
updated-dependencies:
- dependency-name: fonttools
  dependency-version: 4.60.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

18283 of 19408 relevant lines covered (94.2%)

957125.46 hits per line

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

69.81
/rustworkx-core/src/generators/random_graph.rs
1
// Licensed under the Apache License, Version 2.0 (the "License"); you may
2
// not use this file except in compliance with the License. You may obtain
3
// a copy of the License at
4
//
5
//     http://www.apache.org/licenses/LICENSE-2.0
6
//
7
// Unless required by applicable law or agreed to in writing, software
8
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
// License for the specific language governing permissions and limitations
11
// under the License.
12

13
#![allow(clippy::float_cmp)]
14

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

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

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

31
use super::InvalidInputError;
32
use super::star_graph;
33
use crate::geometry::hyperboloid_hyperbolic_distance;
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_n(graph.from_index(x), 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
{
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

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

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

298
            // For directed, create inward edges to a v
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,554✔
303
                    let random: f64 = between.sample(&mut rng);
10,508✔
304
                    let lr: f64 = (1.0 - random).ln();
10,508✔
305
                    w = w + 1 + ((lr / lp) as isize);
10,508✔
306
                    while w >= v && v < num_nodes {
11,146✔
307
                        w -= v;
638✔
308
                        v += 1;
638✔
309
                    }
638✔
310
                    // Skip self-loops
311
                    if v == w {
10,508✔
312
                        w -= v;
×
313
                        v += 1;
×
314
                    }
10,508✔
315
                    if v < num_nodes {
10,508✔
316
                        let v_index = graph.from_index(v as usize);
10,462✔
317
                        let w_index = graph.from_index(w as usize);
10,462✔
318
                        graph.add_edge(w_index, v_index, default_edge_weight());
10,462✔
319
                    }
10,462✔
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,138✔
328
                let random: f64 = between.sample(&mut rng);
377,808✔
329
                let lr: f64 = (1.0 - random).ln();
377,808✔
330
                w = w + 1 + ((lr / lp) as isize);
377,808✔
331
                while w >= v && v < num_nodes {
471,100✔
332
                    w -= v;
93,292✔
333
                    v += 1;
93,292✔
334
                }
93,292✔
335
                if v < num_nodes {
377,808✔
336
                    let v_index = graph.from_index(v as usize);
367,478✔
337
                    let w_index = graph.from_index(w as usize);
367,478✔
338
                    graph.add_edge(v_index, w_index, default_edge_weight());
367,478✔
339
                }
367,478✔
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
{
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
    {
423
        let mut found = false;
4,248✔
424
        for edge in graph.edge_references() {
1,153,008✔
425
            if graph.to_index(edge.source()) == source && graph.to_index(edge.target()) == target {
1,153,008✔
426
                found = true;
360✔
427
                break;
360✔
428
            }
1,152,648✔
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

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,434✔
462
            let u = between.sample(&mut rng);
4,394✔
463
            let v = between.sample(&mut rng);
4,394✔
464
            let u_index = graph.from_index(u);
4,394✔
465
            let v_index = graph.from_index(v);
4,394✔
466
            // avoid self-loops and multi-graphs
467
            if u != v && !find_edge(&graph, u, v) {
4,394✔
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
{
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

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

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
    {
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
{
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,
797
            false,
798
        )?,
×
799
    };
800
    if graph.node_count() < m || graph.node_count() > n {
12✔
801
        return Err(InvalidInputError {});
4✔
802
    }
8✔
803

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_n(x, 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: IndexSet<G::NodeId, foldhash::fast::RandomState> =
400✔
818
            IndexSet::with_capacity_and_hasher(m, foldhash::fast::RandomState::default());
400✔
819
        while targets.len() < m {
1,112,436✔
820
            targets.insert(*repeated_nodes.choose(&mut rng).unwrap());
1,112,036✔
821
        }
1,112,036✔
822
        for target in &targets {
180,000✔
823
            graph.add_edge(source_index, *target, default_edge_weight());
180,000✔
824
        }
180,000✔
825
        repeated_nodes.extend(targets);
400✔
826
        repeated_nodes.extend(vec![source_index; m]);
400✔
827
        source += 1
400✔
828
    }
829
    Ok(graph)
8✔
830
}
12✔
831

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

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

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

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

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

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

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

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

1029
#[cfg(test)]
1030
mod tests {
1031
    use crate::generators::InvalidInputError;
1032
    use crate::generators::{
1033
        barabasi_albert_graph, gnm_random_graph, gnp_random_graph, hyperbolic_random_graph,
1034
        path_graph, random_bipartite_graph, random_geometric_graph, random_regular_graph,
1035
        sbm_random_graph,
1036
    };
1037
    use crate::petgraph;
1038

1039
    // Test gnp_random_graph
1040
    #[test]
1041
    fn test_gnp_random_graph_directed() {
1042
        let g: petgraph::graph::DiGraph<(), ()> =
1043
            gnp_random_graph(20, 0.5, Some(10), || (), || ()).unwrap();
1044
        assert_eq!(g.node_count(), 20);
1045
        assert_eq!(g.edge_count(), 189);
1046
    }
1047

1048
    #[test]
1049
    fn test_random_regular_graph() {
1050
        let g: petgraph::graph::UnGraph<(), ()> =
1051
            random_regular_graph(4, 2, Some(10), || (), || ()).unwrap();
1052
        assert_eq!(g.node_count(), 4);
1053
        assert_eq!(g.edge_count(), 4);
1054
        for node in g.node_indices() {
1055
            assert_eq!(g.edges(node).count(), 2)
1056
        }
1057
    }
1058

1059
    #[test]
1060
    fn test_gnp_random_graph_directed_empty() {
1061
        let g: petgraph::graph::DiGraph<(), ()> =
1062
            gnp_random_graph(20, 0.0, None, || (), || ()).unwrap();
1063
        assert_eq!(g.node_count(), 20);
1064
        assert_eq!(g.edge_count(), 0);
1065
    }
1066

1067
    #[test]
1068
    fn test_gnp_random_graph_directed_complete() {
1069
        let g: petgraph::graph::DiGraph<(), ()> =
1070
            gnp_random_graph(20, 1.0, None, || (), || ()).unwrap();
1071
        assert_eq!(g.node_count(), 20);
1072
        assert_eq!(g.edge_count(), 20 * (20 - 1));
1073
    }
1074

1075
    #[test]
1076
    fn test_gnp_random_graph_undirected() {
1077
        let g: petgraph::graph::UnGraph<(), ()> =
1078
            gnp_random_graph(20, 0.5, Some(10), || (), || ()).unwrap();
1079
        assert_eq!(g.node_count(), 20);
1080
        assert_eq!(g.edge_count(), 105);
1081
    }
1082

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

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

1099
    #[test]
1100
    fn test_gnp_random_graph_error() {
1101
        match gnp_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1102
            0,
1103
            3.0,
1104
            None,
1105
            || (),
1106
            || (),
1107
        ) {
1108
            Ok(_) => panic!("Returned a non-error"),
1109
            Err(e) => assert_eq!(e, InvalidInputError),
1110
        };
1111
    }
1112

1113
    // Test gnm_random_graph
1114

1115
    #[test]
1116
    fn test_gnm_random_graph_directed() {
1117
        let g: petgraph::graph::DiGraph<(), ()> =
1118
            gnm_random_graph(20, 100, None, || (), || ()).unwrap();
1119
        assert_eq!(g.node_count(), 20);
1120
        assert_eq!(g.edge_count(), 100);
1121
    }
1122

1123
    #[test]
1124
    fn test_gnm_random_graph_directed_empty() {
1125
        let g: petgraph::graph::DiGraph<(), ()> =
1126
            gnm_random_graph(20, 0, None, || (), || ()).unwrap();
1127
        assert_eq!(g.node_count(), 20);
1128
        assert_eq!(g.edge_count(), 0);
1129
    }
1130

1131
    #[test]
1132
    fn test_gnm_random_graph_directed_complete() {
1133
        let g: petgraph::graph::DiGraph<(), ()> =
1134
            gnm_random_graph(20, 20 * (20 - 1), None, || (), || ()).unwrap();
1135
        assert_eq!(g.node_count(), 20);
1136
        assert_eq!(g.edge_count(), 20 * (20 - 1));
1137
    }
1138

1139
    #[test]
1140
    fn test_gnm_random_graph_directed_max_edges() {
1141
        let n = 20;
1142
        let max_m = n * (n - 1);
1143
        let g: petgraph::graph::DiGraph<(), ()> =
1144
            gnm_random_graph(n, max_m, None, || (), || ()).unwrap();
1145
        assert_eq!(g.node_count(), n);
1146
        assert_eq!(g.edge_count(), max_m);
1147
        // passing the max edges for the passed number of nodes
1148
        let g: petgraph::graph::DiGraph<(), ()> =
1149
            gnm_random_graph(n, max_m + 1, None, || (), || ()).unwrap();
1150
        assert_eq!(g.node_count(), n);
1151
        assert_eq!(g.edge_count(), max_m);
1152
        // passing a seed when passing max edges has no effect
1153
        let g: petgraph::graph::DiGraph<(), ()> =
1154
            gnm_random_graph(n, max_m, Some(55), || (), || ()).unwrap();
1155
        assert_eq!(g.node_count(), n);
1156
        assert_eq!(g.edge_count(), max_m);
1157
    }
1158

1159
    #[test]
1160
    fn test_gnm_random_graph_error() {
1161
        match gnm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1162
            0,
1163
            0,
1164
            None,
1165
            || (),
1166
            || (),
1167
        ) {
1168
            Ok(_) => panic!("Returned a non-error"),
1169
            Err(e) => assert_eq!(e, InvalidInputError),
1170
        };
1171
    }
1172

1173
    // Test sbm_random_graph
1174
    #[test]
1175
    fn test_sbm_directed_complete_blocks_loops() {
1176
        let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1177
            &[1, 2],
1178
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1179
            true,
1180
            Some(10),
1181
            || (),
1182
            || (),
1183
        )
1184
        .unwrap();
1185
        assert_eq!(g.node_count(), 3);
1186
        assert_eq!(g.edge_count(), 6);
1187
        for (u, v) in [(1, 1), (1, 2), (2, 1), (2, 2), (0, 1), (0, 2)] {
1188
            assert!(g.contains_edge(u.into(), v.into()));
1189
        }
1190
        assert!(!g.contains_edge(1.into(), 0.into()));
1191
        assert!(!g.contains_edge(2.into(), 0.into()));
1192
    }
1193

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

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

1236
    #[test]
1237
    fn test_sbm_undirected_complete_blocks_noloops() {
1238
        let g = sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1239
            &[1, 2],
1240
            &ndarray::arr2(&[[0., 1.], [1., 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(), 3);
1249
        for (u, v) in [(1, 2), (0, 1), (0, 2)] {
1250
            assert!(g.contains_edge(u.into(), v.into()));
1251
        }
1252
        for u in 0..2 {
1253
            assert!(!g.contains_edge(u.into(), u.into()));
1254
        }
1255
    }
1256

1257
    #[test]
1258
    fn test_sbm_bad_array_rows_error() {
1259
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1260
            &[1, 2],
1261
            &ndarray::arr2(&[[0., 1.], [1., 1.], [1., 1.]]).view(),
1262
            true,
1263
            Some(10),
1264
            || (),
1265
            || (),
1266
        ) {
1267
            Ok(_) => panic!("Returned a non-error"),
1268
            Err(e) => assert_eq!(e, InvalidInputError),
1269
        };
1270
    }
1271
    #[test]
1272

1273
    fn test_sbm_bad_array_cols_error() {
1274
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1275
            &[1, 2],
1276
            &ndarray::arr2(&[[0., 1., 1.], [1., 1., 1.]]).view(),
1277
            true,
1278
            Some(10),
1279
            || (),
1280
            || (),
1281
        ) {
1282
            Ok(_) => panic!("Returned a non-error"),
1283
            Err(e) => assert_eq!(e, InvalidInputError),
1284
        };
1285
    }
1286

1287
    #[test]
1288
    fn test_sbm_asymmetric_array_error() {
1289
        match sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1290
            &[1, 2],
1291
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1292
            true,
1293
            Some(10),
1294
            || (),
1295
            || (),
1296
        ) {
1297
            Ok(_) => panic!("Returned a non-error"),
1298
            Err(e) => assert_eq!(e, InvalidInputError),
1299
        };
1300
    }
1301

1302
    #[test]
1303
    fn test_sbm_invalid_probability_error() {
1304
        match sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1305
            &[1, 2],
1306
            &ndarray::arr2(&[[0., 1.], [0., -1.]]).view(),
1307
            true,
1308
            Some(10),
1309
            || (),
1310
            || (),
1311
        ) {
1312
            Ok(_) => panic!("Returned a non-error"),
1313
            Err(e) => assert_eq!(e, InvalidInputError),
1314
        };
1315
    }
1316

1317
    #[test]
1318
    fn test_sbm_empty_error() {
1319
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1320
            &[],
1321
            &ndarray::arr2(&[[]]).view(),
1322
            true,
1323
            Some(10),
1324
            || (),
1325
            || (),
1326
        ) {
1327
            Ok(_) => panic!("Returned a non-error"),
1328
            Err(e) => assert_eq!(e, InvalidInputError),
1329
        };
1330
    }
1331

1332
    // Test random_geometric_graph
1333

1334
    #[test]
1335
    fn test_random_geometric_empty() {
1336
        let g: petgraph::graph::UnGraph<Vec<f64>, ()> =
1337
            random_geometric_graph(20, 0.0, 2, None, 2.0, None, || ()).unwrap();
1338
        assert_eq!(g.node_count(), 20);
1339
        assert_eq!(g.edge_count(), 0);
1340
    }
1341

1342
    #[test]
1343
    fn test_random_geometric_complete() {
1344
        let g: petgraph::graph::UnGraph<Vec<f64>, ()> =
1345
            random_geometric_graph(10, 1.42, 2, None, 2.0, None, || ()).unwrap();
1346
        assert_eq!(g.node_count(), 10);
1347
        assert_eq!(g.edge_count(), 45);
1348
    }
1349

1350
    #[test]
1351
    fn test_random_geometric_bad_num_nodes() {
1352
        match random_geometric_graph::<petgraph::graph::UnGraph<Vec<f64>, ()>, _, ()>(
1353
            0,
1354
            1.0,
1355
            2,
1356
            None,
1357
            2.0,
1358
            None,
1359
            || (),
1360
        ) {
1361
            Ok(_) => panic!("Returned a non-error"),
1362
            Err(e) => assert_eq!(e, InvalidInputError),
1363
        };
1364
    }
1365

1366
    #[test]
1367
    fn test_random_geometric_bad_pos() {
1368
        match random_geometric_graph::<petgraph::graph::UnGraph<Vec<f64>, ()>, _, ()>(
1369
            3,
1370
            0.15,
1371
            3,
1372
            Some(vec![vec![0.5, 0.5]]),
1373
            2.0,
1374
            None,
1375
            || (),
1376
        ) {
1377
            Ok(_) => panic!("Returned a non-error"),
1378
            Err(e) => assert_eq!(e, InvalidInputError),
1379
        };
1380
    }
1381

1382
    #[test]
1383
    fn test_barabasi_albert_graph_seeding() {
1384
        use petgraph::visit::EdgeRef;
1385
        let g1: petgraph::graph::UnGraph<(), ()> =
1386
            barabasi_albert_graph(7, 2, Some(42), None, || (), || ()).unwrap();
1387
        let g2: petgraph::graph::UnGraph<(), ()> =
1388
            barabasi_albert_graph(7, 2, Some(42), None, || (), || ()).unwrap();
1389
        // Same seeds should yield the same graph
1390
        let edges1: std::collections::BTreeSet<_> = g1
1391
            .edge_references()
1392
            .map(|e| (e.source(), e.target()))
1393
            .collect();
1394
        let edges2: std::collections::BTreeSet<_> = g2
1395
            .edge_references()
1396
            .map(|e| (e.source(), e.target()))
1397
            .collect();
1398
        for (e1, e2) in edges1.iter().zip(edges2.iter()) {
1399
            assert_eq!(e1, e2);
1400
        }
1401
    }
1402

1403
    #[test]
1404
    fn test_barabasi_albert_graph_starting_graph() {
1405
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1406
            path_graph(Some(40), None, || (), || (), false).unwrap();
1407
        let graph =
1408
            barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()).unwrap();
1409
        assert_eq!(graph.node_count(), 500);
1410
        assert_eq!(graph.edge_count(), 18439);
1411
    }
1412

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

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

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

1453
    // Test random_bipartite_graph
1454

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

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

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

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

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

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

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

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

1526
    #[test]
1527
    fn test_hyperbolic_random_graph_seeded() {
1528
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1529
            &[
1530
                vec![3_f64.sinh(), 0.],
1531
                vec![-0.5_f64.sinh(), 0.],
1532
                vec![0.5_f64.sinh(), 0.],
1533
                vec![0., 0.],
1534
            ],
1535
            0.75,
1536
            Some(10000.),
1537
            Some(10),
1538
            || (),
1539
            || (),
1540
        )
1541
        .unwrap();
1542
        assert_eq!(g.node_count(), 4);
1543
        assert_eq!(g.edge_count(), 2);
1544
    }
1545

1546
    #[test]
1547
    fn test_hyperbolic_random_graph_threshold() {
1548
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1549
            &[
1550
                vec![3_f64.sinh(), 0.],
1551
                vec![-0.5_f64.sinh(), 0.],
1552
                vec![-1_f64.sinh(), 0.],
1553
            ],
1554
            1.,
1555
            None,
1556
            None,
1557
            || (),
1558
            || (),
1559
        )
1560
        .unwrap();
1561
        assert_eq!(g.node_count(), 3);
1562
        assert_eq!(g.edge_count(), 1);
1563
    }
1564

1565
    #[test]
1566
    fn test_hyperbolic_random_graph_invalid_dim_error() {
1567
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1568
            &[vec![0.]],
1569
            1.,
1570
            None,
1571
            None,
1572
            || (),
1573
            || (),
1574
        ) {
1575
            Ok(_) => panic!("Returned a non-error"),
1576
            Err(e) => assert_eq!(e, InvalidInputError),
1577
        }
1578
    }
1579

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

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

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

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

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