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

Qiskit / rustworkx / 13803210964

12 Mar 2025 03:40AM UTC coverage: 95.313% (-0.01%) from 95.324%
13803210964

push

github

web-flow
Fix some typos in docs (#1394)

18425 of 19331 relevant lines covered (95.31%)

1093520.42 hits per line

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

85.04
/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 std::hash::Hash;
16

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

25
use hashbrown::HashSet;
26
use rand::distributions::{Distribution, Uniform};
27
use rand::prelude::*;
28
use rand_pcg::Pcg64;
29

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

33
/// Generate a G<sub>np</sub> random graph, also known as an
34
/// Erdős-Rényi graph or a binomial graph.
35
///
36
/// For number of nodes `n` and probability `p`, the G<sub>np</sub>
37
/// graph algorithm creates `n` nodes, and for all the `n * (n - 1)` possible edges,
38
/// each edge is created independently with probability `p`.
39
/// In general, for any probability `p`, the expected number of edges returned
40
/// is `m = p * n * (n - 1)`. If `p = 0` or `p = 1`, the returned
41
/// graph is not random and will always be an empty or a complete graph respectively.
42
/// An empty graph has zero edges and a complete directed graph has `n (n - 1)` edges.
43
/// The run time is `O(n + m)` where `m` is the expected number of edges mentioned above.
44
/// When `p = 0`, run time always reduces to `O(n)`, as the lower bound.
45
/// When `p = 1`, run time always goes to `O(n + n * (n - 1))`, as the upper bound.
46
///
47
/// For `0 < p < 1`, the algorithm is based on the implementation of the networkx function
48
/// ``fast_gnp_random_graph``,
49
/// <https://github.com/networkx/networkx/blob/networkx-2.4/networkx/generators/random_graphs.py#L49-L120>
50
///
51
/// Vladimir Batagelj and Ulrik Brandes,
52
///    "Efficient generation of large random networks",
53
///    Phys. Rev. E, 71, 036113, 2005.
54
///
55
/// Arguments:
56
///
57
/// * `num_nodes` - The number of nodes for creating the random graph.
58
/// * `probability` - The probability of creating an edge between two nodes as a float.
59
/// * `seed` - An optional seed to use for the random number generator.
60
/// * `default_node_weight` - A callable that will return the weight to use
61
///     for newly created nodes.
62
/// * `default_edge_weight` - A callable that will return the weight object
63
///     to use for newly created edges.
64
///
65
/// # Example
66
/// ```rust
67
/// use rustworkx_core::petgraph;
68
/// use rustworkx_core::generators::gnp_random_graph;
69
///
70
/// let g: petgraph::graph::DiGraph<(), ()> = gnp_random_graph(
71
///     20,
72
///     1.0,
73
///     None,
74
///     || {()},
75
///     || {()},
76
/// ).unwrap();
77
/// assert_eq!(g.node_count(), 20);
78
/// assert_eq!(g.edge_count(), 20 * (20 - 1));
79
/// ```
80
pub fn gnp_random_graph<G, T, F, H, M>(
10,346✔
81
    num_nodes: usize,
10,346✔
82
    probability: f64,
10,346✔
83
    seed: Option<u64>,
10,346✔
84
    mut default_node_weight: F,
10,346✔
85
    mut default_edge_weight: H,
10,346✔
86
) -> Result<G, InvalidInputError>
10,346✔
87
where
10,346✔
88
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
10,346✔
89
    F: FnMut() -> T,
10,346✔
90
    H: FnMut() -> M,
10,346✔
91
    G::NodeId: Eq + Hash,
10,346✔
92
{
10,346✔
93
    if num_nodes == 0 {
10,346✔
94
        return Err(InvalidInputError {});
4✔
95
    }
10,342✔
96
    let mut rng: Pcg64 = match seed {
10,342✔
97
        Some(seed) => Pcg64::seed_from_u64(seed),
10,326✔
98
        None => Pcg64::from_entropy(),
16✔
99
    };
100
    let mut graph = G::with_capacity(num_nodes, num_nodes);
10,342✔
101
    let directed = graph.is_directed();
10,342✔
102

10,342✔
103
    for _ in 0..num_nodes {
103,874✔
104
        graph.add_node(default_node_weight());
103,874✔
105
    }
103,874✔
106
    if !(0.0..=1.0).contains(&probability) {
10,342✔
107
        return Err(InvalidInputError {});
4✔
108
    }
10,338✔
109

10,338✔
110
    if probability > 0.0 {
10,338✔
111
        if (probability - 1.0).abs() < f64::EPSILON {
10,334✔
112
            for u in 0..num_nodes {
80✔
113
                let start_node = if directed { 0 } else { u + 1 };
80✔
114
                for v in start_node..num_nodes {
1,180✔
115
                    if !directed || u != v {
1,180✔
116
                        // exclude self-loops
1,140✔
117
                        let u_index = graph.from_index(u);
1,140✔
118
                        let v_index = graph.from_index(v);
1,140✔
119
                        graph.add_edge(u_index, v_index, default_edge_weight());
1,140✔
120
                    }
1,140✔
121
                }
122
            }
123
        } else {
124
            let num_nodes: isize = match num_nodes.try_into() {
10,330✔
125
                Ok(nodes) => nodes,
10,330✔
126
                Err(_) => return Err(InvalidInputError {}),
×
127
            };
128
            let lp: f64 = (1.0 - probability).ln();
10,330✔
129
            let between = Uniform::new(0.0, 1.0);
10,330✔
130

10,330✔
131
            // For directed, create inward edges to a v
10,330✔
132
            if directed {
10,330✔
133
                let mut v: isize = 0;
46✔
134
                let mut w: isize = -1;
46✔
135
                while v < num_nodes {
10,550✔
136
                    let random: f64 = between.sample(&mut rng);
10,504✔
137
                    let lr: f64 = (1.0 - random).ln();
10,504✔
138
                    w = w + 1 + ((lr / lp) as isize);
10,504✔
139
                    while w >= v && v < num_nodes {
11,142✔
140
                        w -= v;
638✔
141
                        v += 1;
638✔
142
                    }
638✔
143
                    // Skip self-loops
144
                    if v == w {
10,504✔
145
                        w -= v;
×
146
                        v += 1;
×
147
                    }
10,504✔
148
                    if v < num_nodes {
10,504✔
149
                        let v_index = graph.from_index(v as usize);
10,458✔
150
                        let w_index = graph.from_index(w as usize);
10,458✔
151
                        graph.add_edge(w_index, v_index, default_edge_weight());
10,458✔
152
                    }
10,458✔
153
                }
154
            }
10,284✔
155

156
            // For directed and undirected, create outward edges from a v
157
            // Nodes in graph are from 0,n-1 (start with v as the second node index).
158
            let mut v: isize = 1;
10,330✔
159
            let mut w: isize = -1;
10,330✔
160
            while v < num_nodes {
388,134✔
161
                let random: f64 = between.sample(&mut rng);
377,804✔
162
                let lr: f64 = (1.0 - random).ln();
377,804✔
163
                w = w + 1 + ((lr / lp) as isize);
377,804✔
164
                while w >= v && v < num_nodes {
471,096✔
165
                    w -= v;
93,292✔
166
                    v += 1;
93,292✔
167
                }
93,292✔
168
                if v < num_nodes {
377,804✔
169
                    let v_index = graph.from_index(v as usize);
367,474✔
170
                    let w_index = graph.from_index(w as usize);
367,474✔
171
                    graph.add_edge(v_index, w_index, default_edge_weight());
367,474✔
172
                }
367,474✔
173
            }
174
        }
175
    }
4✔
176
    Ok(graph)
10,338✔
177
}
10,346✔
178

179
// /// Return a `G_{nm}` directed graph, also known as an
180
// /// Erdős-Rényi graph.
181
// ///
182
// /// Generates a random directed graph out of all the possible graphs with `n` nodes and
183
// /// `m` edges. The generated graph will not be a multigraph and will not have self loops.
184
// ///
185
// /// For `n` nodes, the maximum edges that can be returned is `n (n - 1)`.
186
// /// Passing `m` higher than that will still return the maximum number of edges.
187
// /// If `m = 0`, the returned graph will always be empty (no edges).
188
// /// When a seed is provided, the results are reproducible. Passing a seed when `m = 0`
189
// /// or `m >= n (n - 1)` has no effect, as the result will always be an empty or a complete graph respectively.
190
// ///
191
// /// This algorithm has a time complexity of `O(n + m)`
192

193
/// Generate a G<sub>nm</sub> random graph, also known as an
194
/// Erdős-Rényi graph.
195
///
196
/// Generates a random directed graph out of all the possible graphs with `n` nodes and
197
/// `m` edges. The generated graph will not be a multigraph and will not have self loops.
198
///
199
/// For `n` nodes, the maximum edges that can be returned is `n * (n - 1)`.
200
/// Passing `m` higher than that will still return the maximum number of edges.
201
/// If `m = 0`, the returned graph will always be empty (no edges).
202
/// When a seed is provided, the results are reproducible. Passing a seed when `m = 0`
203
/// or `m >= n * (n - 1)` has no effect, as the result will always be an empty or a
204
/// complete graph respectively.
205
///
206
/// This algorithm has a time complexity of `O(n + m)`
207
///
208
/// Arguments:
209
///
210
/// * `num_nodes` - The number of nodes to create in the graph.
211
/// * `num_edges` - The number of edges to create in the graph.
212
/// * `seed` - An optional seed to use for the random number generator.
213
/// * `default_node_weight` - A callable that will return the weight to use
214
///     for newly created nodes.
215
/// * `default_edge_weight` - A callable that will return the weight object
216
///     to use for newly created edges.
217
///
218
/// # Example
219
/// ```rust
220
/// use rustworkx_core::petgraph;
221
/// use rustworkx_core::generators::gnm_random_graph;
222
///
223
/// let g: petgraph::graph::DiGraph<(), ()> = gnm_random_graph(
224
///     20,
225
///     12,
226
///     None,
227
///     || {()},
228
///     || {()},
229
/// ).unwrap();
230
/// assert_eq!(g.node_count(), 20);
231
/// assert_eq!(g.edge_count(), 12);
232
/// ```
233
pub fn gnm_random_graph<G, T, F, H, M>(
58✔
234
    num_nodes: usize,
58✔
235
    num_edges: usize,
58✔
236
    seed: Option<u64>,
58✔
237
    mut default_node_weight: F,
58✔
238
    mut default_edge_weight: H,
58✔
239
) -> Result<G, InvalidInputError>
58✔
240
where
58✔
241
    G: GraphProp + Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
58✔
242
    F: FnMut() -> T,
58✔
243
    H: FnMut() -> M,
58✔
244
    for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
58✔
245
    G::NodeId: Eq + Hash,
58✔
246
{
58✔
247
    if num_nodes == 0 {
58✔
248
        return Err(InvalidInputError {});
4✔
249
    }
54✔
250

251
    fn find_edge<G>(graph: &G, source: usize, target: usize) -> bool
4,256✔
252
    where
4,256✔
253
        G: GraphBase + NodeIndexable,
4,256✔
254
        for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
4,256✔
255
    {
4,256✔
256
        let mut found = false;
4,256✔
257
        for edge in graph.edge_references() {
1,150,994✔
258
            if graph.to_index(edge.source()) == source && graph.to_index(edge.target()) == target {
1,150,994✔
259
                found = true;
368✔
260
                break;
368✔
261
            }
1,150,626✔
262
        }
263
        found
4,256✔
264
    }
4,256✔
265

266
    let mut rng: Pcg64 = match seed {
54✔
267
        Some(seed) => Pcg64::seed_from_u64(seed),
20✔
268
        None => Pcg64::from_entropy(),
34✔
269
    };
270
    let mut graph = G::with_capacity(num_nodes, num_edges);
54✔
271
    let directed = graph.is_directed();
54✔
272

54✔
273
    for _ in 0..num_nodes {
1,132✔
274
        graph.add_node(default_node_weight());
1,132✔
275
    }
1,132✔
276
    // if number of edges to be created is >= max,
277
    // avoid randomly missed trials and directly add edges between every node
278
    let div_by = if directed { 1 } else { 2 };
54✔
279
    if num_edges >= num_nodes * (num_nodes - 1) / div_by {
54✔
280
        for u in 0..num_nodes {
246✔
281
            let start_node = if directed { 0 } else { u + 1 };
246✔
282
            for v in start_node..num_nodes {
3,546✔
283
                // avoid self-loops
284
                if !directed || u != v {
3,546✔
285
                    let u_index = graph.from_index(u);
3,426✔
286
                    let v_index = graph.from_index(v);
3,426✔
287
                    graph.add_edge(u_index, v_index, default_edge_weight());
3,426✔
288
                }
3,426✔
289
            }
290
        }
291
    } else {
292
        let mut created_edges: usize = 0;
40✔
293
        let between = Uniform::new(0, num_nodes);
40✔
294
        while created_edges < num_edges {
4,436✔
295
            let u = between.sample(&mut rng);
4,396✔
296
            let v = between.sample(&mut rng);
4,396✔
297
            let u_index = graph.from_index(u);
4,396✔
298
            let v_index = graph.from_index(v);
4,396✔
299
            // avoid self-loops and multi-graphs
4,396✔
300
            if u != v && !find_edge(&graph, u, v) {
4,396✔
301
                graph.add_edge(u_index, v_index, default_edge_weight());
3,888✔
302
                created_edges += 1;
3,888✔
303
            }
3,888✔
304
        }
305
    }
306
    Ok(graph)
54✔
307
}
58✔
308

309
/// Generate a graph from the stochastic block model.
310
///
311
/// The stochastic block model is a generalization of the G<sub>np</sub> random graph
312
/// (see [gnp_random_graph] ). The connection probability of
313
/// nodes `u` and `v` depends on their block and is given by
314
/// `probabilities[blocks[u]][blocks[v]]`, where `blocks[u]` is the block membership
315
/// of vertex `u`. The number of nodes and the number of blocks are inferred from
316
/// `sizes`.
317
///
318
/// Arguments:
319
///
320
/// * `sizes` - Number of nodes in each block.
321
/// * `probabilities` - B x B array that contains the connection probability between
322
///     nodes of different blocks. Must be symmetric for undirected graphs.
323
/// * `loops` - Determines whether the graph can have loops or not.
324
/// * `seed` - An optional seed to use for the random number generator.
325
/// * `default_node_weight` - A callable that will return the weight to use
326
///     for newly created nodes.
327
/// * `default_edge_weight` - A callable that will return the weight object
328
///     to use for newly created edges.
329
///
330
/// # Example
331
/// ```rust
332
/// use ndarray::arr2;
333
/// use rustworkx_core::petgraph;
334
/// use rustworkx_core::generators::sbm_random_graph;
335
///
336
/// let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
337
///     &vec![1, 2],
338
///     &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
339
///     true,
340
///     Some(10),
341
///     || (),
342
///     || (),
343
/// )
344
/// .unwrap();
345
/// assert_eq!(g.node_count(), 3);
346
/// assert_eq!(g.edge_count(), 6);
347
/// ```
348
pub fn sbm_random_graph<G, T, F, H, M>(
26✔
349
    sizes: &[usize],
26✔
350
    probabilities: &ndarray::ArrayView2<f64>,
26✔
351
    loops: bool,
26✔
352
    seed: Option<u64>,
26✔
353
    mut default_node_weight: F,
26✔
354
    mut default_edge_weight: H,
26✔
355
) -> Result<G, InvalidInputError>
26✔
356
where
26✔
357
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
26✔
358
    F: FnMut() -> T,
26✔
359
    H: FnMut() -> M,
26✔
360
    G::NodeId: Eq + Hash,
26✔
361
{
26✔
362
    let num_nodes: usize = sizes.iter().sum();
26✔
363
    if num_nodes == 0 {
26✔
364
        return Err(InvalidInputError {});
4✔
365
    }
22✔
366
    let num_communities = sizes.len();
22✔
367
    if probabilities.nrows() != num_communities
22✔
368
        || probabilities.ncols() != num_communities
20✔
369
        || probabilities.iter().any(|&x| !(0. ..=1.).contains(&x))
60✔
370
    {
371
        return Err(InvalidInputError {});
12✔
372
    }
10✔
373

10✔
374
    let mut graph = G::with_capacity(num_nodes, num_nodes);
10✔
375
    let directed = graph.is_directed();
10✔
376
    if !directed && !symmetric_array(probabilities) {
10✔
377
        return Err(InvalidInputError {});
2✔
378
    }
8✔
379

8✔
380
    for _ in 0..num_nodes {
24✔
381
        graph.add_node(default_node_weight());
24✔
382
    }
24✔
383
    let mut rng: Pcg64 = match seed {
8✔
384
        Some(seed) => Pcg64::seed_from_u64(seed),
×
385
        None => Pcg64::from_entropy(),
8✔
386
    };
387
    let mut blocks = Vec::new();
8✔
388
    {
8✔
389
        let mut block = 0;
8✔
390
        let mut vertices_left = sizes[0];
8✔
391
        for _ in 0..num_nodes {
8✔
392
            while vertices_left == 0 {
32✔
393
                block += 1;
8✔
394
                vertices_left = sizes[block];
8✔
395
            }
8✔
396
            blocks.push(block);
24✔
397
            vertices_left -= 1;
24✔
398
        }
399
    }
400

401
    let between = Uniform::new(0.0, 1.0);
8✔
402
    for v in 0..(if directed || loops {
22✔
403
        num_nodes
6✔
404
    } else {
405
        num_nodes - 1
2✔
406
    }) {
407
        for w in ((if directed { 0 } else { v })..num_nodes).filter(|&w| w != v || loops) {
58✔
408
            if &between.sample(&mut rng)
48✔
409
                < probabilities.get((blocks[v], blocks[w])).unwrap_or(&0_f64)
48✔
410
            {
26✔
411
                graph.add_edge(
26✔
412
                    graph.from_index(v),
26✔
413
                    graph.from_index(w),
26✔
414
                    default_edge_weight(),
26✔
415
                );
26✔
416
            }
26✔
417
        }
418
    }
419
    Ok(graph)
8✔
420
}
26✔
421

422
fn symmetric_array<T: std::cmp::PartialEq>(mat: &ArrayView2<T>) -> bool {
6✔
423
    let n = mat.nrows();
6✔
424
    for (i, row) in mat.rows().into_iter().enumerate().take(n - 1) {
6✔
425
        for (j, m_ij) in row.iter().enumerate().skip(i + 1) {
6✔
426
            if m_ij != mat.get((j, i)).unwrap() {
6✔
427
                return false;
2✔
428
            }
4✔
429
        }
430
    }
431
    true
4✔
432
}
6✔
433

434
#[inline]
435
fn pnorm(x: f64, p: f64) -> f64 {
×
436
    if p == 1.0 || p == f64::INFINITY {
×
437
        x.abs()
×
438
    } else if p == 2.0 {
×
439
        x * x
×
440
    } else {
441
        x.abs().powf(p)
×
442
    }
443
}
×
444

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

×
448
    if p == f64::INFINITY {
×
449
        it.fold(-1.0, |max, x| if x > max { x } else { max })
×
450
    } else {
451
        it.sum()
×
452
    }
453
}
×
454

455
/// Generate a random geometric graph in the unit cube of dimensions `dim`.
456
///
457
/// The random geometric graph model places `num_nodes` nodes uniformly at
458
/// random in the unit cube. Two nodes are joined by an edge if the
459
/// distance between the nodes is at most `radius`.
460
///
461
/// Each node has a node attribute ``'pos'`` that stores the
462
/// position of that node in Euclidean space as provided by the
463
/// ``pos`` keyword argument or, if ``pos`` was not provided, as
464
/// generated by this function.
465
///
466
/// Arguments
467
///
468
/// * `num_nodes` - The number of nodes to create in the graph.
469
/// * `radius` - Distance threshold value.
470
/// * `dim` - Dimension of node positions. Default: 2
471
/// * `pos` - Optional list with node positions as values.
472
/// * `p` - Which Minkowski distance metric to use.  `p` has to meet the condition
473
///     ``1 <= p <= infinity``.
474
///     If this argument is not specified, the L<sup>2</sup> metric
475
///     (the Euclidean distance metric), `p = 2` is used.
476
/// * `seed` - An optional seed to use for the random number generator.
477
/// * `default_edge_weight` - A callable that will return the weight object
478
///     to use for newly created edges.
479
///
480
/// # Example
481
/// ```rust
482
/// use rustworkx_core::petgraph;
483
/// use rustworkx_core::generators::random_geometric_graph;
484
///
485
/// let g: petgraph::graph::UnGraph<Vec<f64>, ()> = random_geometric_graph(
486
///     10,
487
///     1.42,
488
///     2,
489
///     None,
490
///     2.0,
491
///     None,
492
///     || {()},
493
/// ).unwrap();
494
/// assert_eq!(g.node_count(), 10);
495
/// assert_eq!(g.edge_count(), 45);
496
/// ```
497
pub fn random_geometric_graph<G, H, M>(
×
498
    num_nodes: usize,
×
499
    radius: f64,
×
500
    dim: usize,
×
501
    pos: Option<Vec<Vec<f64>>>,
×
502
    p: f64,
×
503
    seed: Option<u64>,
×
504
    mut default_edge_weight: H,
×
505
) -> Result<G, InvalidInputError>
×
506
where
×
507
    G: GraphBase + Build + Create + Data<NodeWeight = Vec<f64>, EdgeWeight = M> + NodeIndexable,
×
508
    H: FnMut() -> M,
×
509
    for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgeReferences,
×
510
    G::NodeId: Eq + Hash,
×
511
{
×
512
    if num_nodes == 0 {
×
513
        return Err(InvalidInputError {});
×
514
    }
×
515
    let mut rng: Pcg64 = match seed {
×
516
        Some(seed) => Pcg64::seed_from_u64(seed),
×
517
        None => Pcg64::from_entropy(),
×
518
    };
519
    let mut graph = G::with_capacity(num_nodes, num_nodes);
×
520

×
521
    let radius_p = pnorm(radius, p);
×
522
    let dist = Uniform::new(0.0, 1.0);
×
523
    let pos = pos.unwrap_or_else(|| {
×
524
        (0..num_nodes)
×
525
            .map(|_| (0..dim).map(|_| dist.sample(&mut rng)).collect())
×
526
            .collect()
×
527
    });
×
528
    if num_nodes != pos.len() {
×
529
        return Err(InvalidInputError {});
×
530
    }
×
531
    for pval in pos.iter() {
×
532
        graph.add_node(pval.clone());
×
533
    }
×
534
    for u in 0..(num_nodes - 1) {
×
535
        for v in (u + 1)..num_nodes {
×
536
            if distance(&pos[u], &pos[v], p) < radius_p {
×
537
                graph.add_edge(
×
538
                    graph.from_index(u),
×
539
                    graph.from_index(v),
×
540
                    default_edge_weight(),
×
541
                );
×
542
            }
×
543
        }
544
    }
545
    Ok(graph)
×
546
}
×
547

548
/// Generate a random Barabási–Albert preferential attachment algorithm
549
///
550
/// A graph of `n` nodes is grown by attaching new nodes each with `m`
551
/// edges that are preferentially attached to existing nodes with high degree.
552
/// If the graph is directed for the purposes of the extension algorithm all
553
/// edges are treated as weak (meaning both incoming and outgoing).
554
///
555
/// The algorithm implemented in this function is described in:
556
///
557
/// A. L. Barabási and R. Albert "Emergence of scaling in random networks",
558
/// Science 286, pp 509-512, 1999.
559
///
560
/// Arguments:
561
///
562
/// * `n` - The number of nodes to extend the graph to.
563
/// * `m` - The number of edges to attach from a new node to existing nodes.
564
/// * `seed` - An optional seed to use for the random number generator.
565
/// * `initial_graph` - An optional starting graph to expand, if not specified
566
///     a star graph of `m` nodes is generated and used. If specified the input
567
///     graph is mutated by this function and is expected to be moved into this
568
///     function.
569
/// * `default_node_weight` - A callable that will return the weight to use
570
///     for newly created nodes.
571
/// * `default_edge_weight` - A callable that will return the weight object
572
///     to use for newly created edges.
573
///
574
/// An `InvalidInput` error is returned under the following conditions. If `m < 1`
575
/// or `m >= n` and if an `initial_graph` is specified and the number of nodes in
576
/// `initial_graph` is `< m` or `> n`.
577
///
578
/// # Example
579
/// ```rust
580
/// use rustworkx_core::petgraph;
581
/// use rustworkx_core::generators::barabasi_albert_graph;
582
/// use rustworkx_core::generators::star_graph;
583
///
584
/// let graph: petgraph::graph::UnGraph<(), ()> = barabasi_albert_graph(
585
///     20,
586
///     12,
587
///     Some(42),
588
///     None,
589
///     || {()},
590
///     || {()},
591
/// ).unwrap();
592
/// assert_eq!(graph.node_count(), 20);
593
/// assert_eq!(graph.edge_count(), 107);
594
/// ```
595
pub fn barabasi_albert_graph<G, T, F, H, M>(
12✔
596
    n: usize,
12✔
597
    m: usize,
12✔
598
    seed: Option<u64>,
12✔
599
    initial_graph: Option<G>,
12✔
600
    mut default_node_weight: F,
12✔
601
    mut default_edge_weight: H,
12✔
602
) -> Result<G, InvalidInputError>
12✔
603
where
12✔
604
    G: Data<NodeWeight = T, EdgeWeight = M>
12✔
605
        + NodeIndexable
12✔
606
        + GraphProp
12✔
607
        + NodeCount
12✔
608
        + Build
12✔
609
        + Create,
12✔
610
    for<'b> &'b G: GraphBase<NodeId = G::NodeId> + IntoEdgesDirected + IntoNodeIdentifiers,
12✔
611
    F: FnMut() -> T,
12✔
612
    H: FnMut() -> M,
12✔
613
    G::NodeId: Eq + Hash,
12✔
614
{
12✔
615
    if m < 1 || m >= n {
12✔
616
        return Err(InvalidInputError {});
×
617
    }
12✔
618
    let mut rng: Pcg64 = match seed {
12✔
619
        Some(seed) => Pcg64::seed_from_u64(seed),
8✔
620
        None => Pcg64::from_entropy(),
4✔
621
    };
622
    let mut graph = match initial_graph {
12✔
623
        Some(initial_graph) => initial_graph,
8✔
624
        None => star_graph(
4✔
625
            Some(m),
4✔
626
            None,
4✔
627
            &mut default_node_weight,
4✔
628
            &mut default_edge_weight,
4✔
629
            false,
4✔
630
            false,
4✔
631
        )?,
4✔
632
    };
633
    if graph.node_count() < m || graph.node_count() > n {
12✔
634
        return Err(InvalidInputError {});
4✔
635
    }
8✔
636

8✔
637
    let mut repeated_nodes: Vec<G::NodeId> = graph
8✔
638
        .node_identifiers()
8✔
639
        .flat_map(|x| {
3,600✔
640
            let degree = graph
3,600✔
641
                .edges_directed(x, Outgoing)
3,600✔
642
                .chain(graph.edges_directed(x, Incoming))
3,600✔
643
                .count();
3,600✔
644
            std::iter::repeat(x).take(degree)
3,600✔
645
        })
3,600✔
646
        .collect();
8✔
647
    let mut source = graph.node_count();
8✔
648
    while source < n {
408✔
649
        let source_index = graph.add_node(default_node_weight());
400✔
650
        let mut targets: HashSet<G::NodeId> = HashSet::with_capacity(m);
400✔
651
        while targets.len() < m {
1,095,640✔
652
            targets.insert(*repeated_nodes.choose(&mut rng).unwrap());
1,095,240✔
653
        }
1,095,240✔
654
        for target in &targets {
180,400✔
655
            graph.add_edge(source_index, *target, default_edge_weight());
180,000✔
656
        }
180,000✔
657
        repeated_nodes.extend(targets);
400✔
658
        repeated_nodes.extend(vec![source_index; m]);
400✔
659
        source += 1
400✔
660
    }
661
    Ok(graph)
8✔
662
}
12✔
663

664
/// Generate a random bipartite graph.
665
///
666
/// A bipartite graph is a graph whose nodes can be divided into two disjoint sets,
667
/// informally called "left nodes" and "right nodes", so that every edge connects
668
/// some left node and some right node.
669
///
670
/// Given a number `n` of left nodes, a number `m` of right nodes, and a probability `p`,
671
/// the algorithm creates a graph with `n + m` nodes. For all the `n * m` possible edges,
672
/// each edge is created independently with probability `p`.
673
///
674
/// Arguments:
675
///
676
/// * `num_l_nodes` - The number of "left" nodes in the random bipartite graph.
677
/// * `num_r_nodes` - The number of "right" nodes in the random bipartite graph.
678
/// * `probability` - The probability of creating an edge between two nodes as a float.
679
/// * `seed` - An optional seed to use for the random number generator.
680
/// * `default_node_weight` - A callable that will return the weight to use
681
///     for newly created nodes.
682
/// * `default_edge_weight` - A callable that will return the weight object
683
///     to use for newly created edges.
684
///
685
/// # Example
686
/// ```rust
687
/// use rustworkx_core::petgraph;
688
/// use rustworkx_core::generators::random_bipartite_graph;
689
///
690
/// let g: petgraph::graph::DiGraph<(), ()> = random_bipartite_graph(
691
///     20,
692
///     20,
693
///     1.0,
694
///     None,
695
///     || {()},
696
///     || {()},
697
/// ).unwrap();
698
/// assert_eq!(g.node_count(), 20 + 20);
699
/// assert_eq!(g.edge_count(), 20 * 20);
700
/// ```
701
pub fn random_bipartite_graph<G, T, F, H, M>(
28✔
702
    num_l_nodes: usize,
28✔
703
    num_r_nodes: usize,
28✔
704
    probability: f64,
28✔
705
    seed: Option<u64>,
28✔
706
    mut default_node_weight: F,
28✔
707
    mut default_edge_weight: H,
28✔
708
) -> Result<G, InvalidInputError>
28✔
709
where
28✔
710
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
28✔
711
    F: FnMut() -> T,
28✔
712
    H: FnMut() -> M,
28✔
713
    G::NodeId: Eq + Hash,
28✔
714
{
28✔
715
    if num_l_nodes == 0 && num_r_nodes == 0 {
28✔
716
        return Err(InvalidInputError {});
4✔
717
    }
24✔
718
    if !(0.0..=1.0).contains(&probability) {
24✔
719
        return Err(InvalidInputError {});
4✔
720
    }
20✔
721

722
    let mut rng: Pcg64 = match seed {
20✔
723
        Some(seed) => Pcg64::seed_from_u64(seed),
8✔
724
        None => Pcg64::from_entropy(),
12✔
725
    };
726
    let mut graph = G::with_capacity(num_l_nodes + num_r_nodes, num_l_nodes + num_r_nodes);
20✔
727

20✔
728
    for _ in 0..num_l_nodes + num_r_nodes {
260✔
729
        graph.add_node(default_node_weight());
260✔
730
    }
260✔
731

732
    let between = Uniform::new(0.0, 1.0);
20✔
733
    for v in 0..num_l_nodes {
140✔
734
        for w in 0..num_r_nodes {
800✔
735
            let random: f64 = between.sample(&mut rng);
800✔
736
            if random < probability {
800✔
737
                graph.add_edge(
388✔
738
                    graph.from_index(v),
388✔
739
                    graph.from_index(num_l_nodes + w),
388✔
740
                    default_edge_weight(),
388✔
741
                );
388✔
742
            }
412✔
743
        }
744
    }
745
    Ok(graph)
20✔
746
}
28✔
747

748
/// Generate a hyperbolic random undirected graph (also called hyperbolic geometric graph).
749
///
750
/// The hyperbolic random graph model connects pairs of nodes with a probability
751
/// that decreases as their hyperbolic distance increases.
752
///
753
/// The number of nodes and the dimension are inferred from the coordinates `pos` of the
754
/// hyperboloid model. The "time" coordinates are inferred from the others, meaning that
755
/// at least 2 coordinates must be provided per node. If `beta` is `None`, all pairs of
756
/// nodes with a distance smaller than ``r`` are connected.
757
///
758
/// Arguments:
759
///
760
/// * `pos` - Hyperboloid model coordinates of the nodes `[p_1, p_2, ...]` where `p_i` is the
761
///     position of node i. The "time" coordinates are inferred.
762
/// * `beta` - Sigmoid sharpness (nonnegative) of the connection probability.
763
/// * `r` - Distance at which the connection probability is 0.5 for the probabilistic model.
764
///     Threshold when `beta` is `None`.
765
/// * `seed` - An optional seed to use for the random number generator.
766
/// * `default_node_weight` - A callable that will return the weight to use
767
///     for newly created nodes.
768
/// * `default_edge_weight` - A callable that will return the weight object
769
///     to use for newly created edges.
770
///
771
/// # Example
772
/// ```rust
773
/// use rustworkx_core::petgraph;
774
/// use rustworkx_core::generators::hyperbolic_random_graph;
775
///
776
/// let g: petgraph::graph::UnGraph<(), ()> = hyperbolic_random_graph(
777
///     &[vec![3_f64.sinh(), 0.],
778
///       vec![-0.5_f64.sinh(), 0.],
779
///       vec![-1_f64.sinh(), 0.]],
780
///     2.,
781
///     None,
782
///     None,
783
///     || {()},
784
///     || {()},
785
/// ).unwrap();
786
/// assert_eq!(g.node_count(), 3);
787
/// assert_eq!(g.edge_count(), 1);
788
/// ```
789
pub fn hyperbolic_random_graph<G, T, F, H, M>(
16✔
790
    pos: &[Vec<f64>],
16✔
791
    r: f64,
16✔
792
    beta: Option<f64>,
16✔
793
    seed: Option<u64>,
16✔
794
    mut default_node_weight: F,
16✔
795
    mut default_edge_weight: H,
16✔
796
) -> Result<G, InvalidInputError>
16✔
797
where
16✔
798
    G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
16✔
799
    F: FnMut() -> T,
16✔
800
    H: FnMut() -> M,
16✔
801
    G::NodeId: Eq + Hash,
16✔
802
{
16✔
803
    let num_nodes = pos.len();
16✔
804
    if num_nodes == 0 {
16✔
805
        return Err(InvalidInputError {});
2✔
806
    }
14✔
807
    if pos
14✔
808
        .iter()
14✔
809
        .any(|xs| xs.iter().any(|x| x.is_nan() || x.is_infinite()))
58✔
810
    {
811
        return Err(InvalidInputError {});
×
812
    }
14✔
813
    let dim = pos[0].len();
14✔
814
    if dim < 2 || pos.iter().any(|x| x.len() != dim) {
28✔
815
        return Err(InvalidInputError {});
2✔
816
    }
12✔
817
    if beta.is_some_and(|b| b < 0. || b.is_nan()) {
12✔
818
        return Err(InvalidInputError {});
2✔
819
    }
10✔
820
    if r < 0. || r.is_nan() {
10✔
821
        return Err(InvalidInputError {});
2✔
822
    }
8✔
823

824
    let mut rng: Pcg64 = match seed {
8✔
825
        Some(seed) => Pcg64::seed_from_u64(seed),
4✔
826
        None => Pcg64::from_entropy(),
4✔
827
    };
828
    let mut graph = G::with_capacity(num_nodes, num_nodes);
8✔
829
    if graph.is_directed() {
8✔
830
        return Err(InvalidInputError {});
×
831
    }
8✔
832

8✔
833
    for _ in 0..num_nodes {
16✔
834
        graph.add_node(default_node_weight());
16✔
835
    }
16✔
836

837
    let between = Uniform::new(0.0, 1.0);
8✔
838
    for (v, p1) in pos.iter().enumerate().take(num_nodes - 1) {
8✔
839
        for (w, p2) in pos.iter().enumerate().skip(v + 1) {
8✔
840
            let dist = hyperbolic_distance(p1, p2);
8✔
841
            let is_edge = match beta {
8✔
842
                Some(b) => {
4✔
843
                    let prob_inverse = (b / 2. * (dist - r)).exp() + 1.;
4✔
844
                    let u: f64 = between.sample(&mut rng);
4✔
845
                    prob_inverse * u < 1.
4✔
846
                }
847
                None => dist < r,
4✔
848
            };
849
            if is_edge {
8✔
850
                graph.add_edge(
4✔
851
                    graph.from_index(v),
4✔
852
                    graph.from_index(w),
4✔
853
                    default_edge_weight(),
4✔
854
                );
4✔
855
            }
4✔
856
        }
857
    }
858
    Ok(graph)
8✔
859
}
16✔
860

861
#[inline]
862
fn hyperbolic_distance(x: &[f64], y: &[f64]) -> f64 {
8✔
863
    let mut sum_squared_x = 0.;
8✔
864
    let mut sum_squared_y = 0.;
8✔
865
    let mut inner_product = 0.;
8✔
866
    for (x_i, y_i) in x.iter().zip(y.iter()) {
16✔
867
        if x_i.is_infinite() || y_i.is_infinite() || x_i.is_nan() || y_i.is_nan() {
16✔
868
            return f64::NAN;
×
869
        }
16✔
870
        sum_squared_x = x_i.mul_add(*x_i, sum_squared_x);
16✔
871
        sum_squared_y = y_i.mul_add(*y_i, sum_squared_y);
16✔
872
        inner_product = x_i.mul_add(*y_i, inner_product);
16✔
873
    }
874
    let arg = (1. + sum_squared_x).sqrt() * (1. + sum_squared_y).sqrt() - inner_product;
8✔
875
    if arg < 1. {
8✔
876
        0.
×
877
    } else {
878
        arg.acosh()
8✔
879
    }
880
}
8✔
881

882
#[cfg(test)]
883
mod tests {
884
    use crate::generators::InvalidInputError;
885
    use crate::generators::{
886
        barabasi_albert_graph, gnm_random_graph, gnp_random_graph, hyperbolic_random_graph,
887
        path_graph, random_bipartite_graph, random_geometric_graph, sbm_random_graph,
888
    };
889
    use crate::petgraph;
890

891
    use super::hyperbolic_distance;
892

893
    // Test gnp_random_graph
894

895
    #[test]
896
    fn test_gnp_random_graph_directed() {
897
        let g: petgraph::graph::DiGraph<(), ()> =
898
            gnp_random_graph(20, 0.5, Some(10), || (), || ()).unwrap();
899
        assert_eq!(g.node_count(), 20);
900
        assert_eq!(g.edge_count(), 189);
901
    }
902

903
    #[test]
904
    fn test_gnp_random_graph_directed_empty() {
905
        let g: petgraph::graph::DiGraph<(), ()> =
906
            gnp_random_graph(20, 0.0, None, || (), || ()).unwrap();
907
        assert_eq!(g.node_count(), 20);
908
        assert_eq!(g.edge_count(), 0);
909
    }
910

911
    #[test]
912
    fn test_gnp_random_graph_directed_complete() {
913
        let g: petgraph::graph::DiGraph<(), ()> =
914
            gnp_random_graph(20, 1.0, None, || (), || ()).unwrap();
915
        assert_eq!(g.node_count(), 20);
916
        assert_eq!(g.edge_count(), 20 * (20 - 1));
917
    }
918

919
    #[test]
920
    fn test_gnp_random_graph_undirected() {
921
        let g: petgraph::graph::UnGraph<(), ()> =
922
            gnp_random_graph(20, 0.5, Some(10), || (), || ()).unwrap();
923
        assert_eq!(g.node_count(), 20);
924
        assert_eq!(g.edge_count(), 105);
925
    }
926

927
    #[test]
928
    fn test_gnp_random_graph_undirected_empty() {
929
        let g: petgraph::graph::UnGraph<(), ()> =
930
            gnp_random_graph(20, 0.0, None, || (), || ()).unwrap();
931
        assert_eq!(g.node_count(), 20);
932
        assert_eq!(g.edge_count(), 0);
933
    }
934

935
    #[test]
936
    fn test_gnp_random_graph_undirected_complete() {
937
        let g: petgraph::graph::UnGraph<(), ()> =
938
            gnp_random_graph(20, 1.0, None, || (), || ()).unwrap();
939
        assert_eq!(g.node_count(), 20);
940
        assert_eq!(g.edge_count(), 20 * (20 - 1) / 2);
941
    }
942

943
    #[test]
944
    fn test_gnp_random_graph_error() {
945
        match gnp_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
946
            0,
947
            3.0,
948
            None,
949
            || (),
950
            || (),
951
        ) {
952
            Ok(_) => panic!("Returned a non-error"),
953
            Err(e) => assert_eq!(e, InvalidInputError),
954
        };
955
    }
956

957
    // Test gnm_random_graph
958

959
    #[test]
960
    fn test_gnm_random_graph_directed() {
961
        let g: petgraph::graph::DiGraph<(), ()> =
962
            gnm_random_graph(20, 100, None, || (), || ()).unwrap();
963
        assert_eq!(g.node_count(), 20);
964
        assert_eq!(g.edge_count(), 100);
965
    }
966

967
    #[test]
968
    fn test_gnm_random_graph_directed_empty() {
969
        let g: petgraph::graph::DiGraph<(), ()> =
970
            gnm_random_graph(20, 0, None, || (), || ()).unwrap();
971
        assert_eq!(g.node_count(), 20);
972
        assert_eq!(g.edge_count(), 0);
973
    }
974

975
    #[test]
976
    fn test_gnm_random_graph_directed_complete() {
977
        let g: petgraph::graph::DiGraph<(), ()> =
978
            gnm_random_graph(20, 20 * (20 - 1), None, || (), || ()).unwrap();
979
        assert_eq!(g.node_count(), 20);
980
        assert_eq!(g.edge_count(), 20 * (20 - 1));
981
    }
982

983
    #[test]
984
    fn test_gnm_random_graph_directed_max_edges() {
985
        let n = 20;
986
        let max_m = n * (n - 1);
987
        let g: petgraph::graph::DiGraph<(), ()> =
988
            gnm_random_graph(n, max_m, None, || (), || ()).unwrap();
989
        assert_eq!(g.node_count(), n);
990
        assert_eq!(g.edge_count(), max_m);
991
        // passing the max edges for the passed number of nodes
992
        let g: petgraph::graph::DiGraph<(), ()> =
993
            gnm_random_graph(n, max_m + 1, None, || (), || ()).unwrap();
994
        assert_eq!(g.node_count(), n);
995
        assert_eq!(g.edge_count(), max_m);
996
        // passing a seed when passing max edges has no effect
997
        let g: petgraph::graph::DiGraph<(), ()> =
998
            gnm_random_graph(n, max_m, Some(55), || (), || ()).unwrap();
999
        assert_eq!(g.node_count(), n);
1000
        assert_eq!(g.edge_count(), max_m);
1001
    }
1002

1003
    #[test]
1004
    fn test_gnm_random_graph_error() {
1005
        match gnm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1006
            0,
1007
            0,
1008
            None,
1009
            || (),
1010
            || (),
1011
        ) {
1012
            Ok(_) => panic!("Returned a non-error"),
1013
            Err(e) => assert_eq!(e, InvalidInputError),
1014
        };
1015
    }
1016

1017
    // Test sbm_random_graph
1018
    #[test]
1019
    fn test_sbm_directed_complete_blocks_loops() {
1020
        let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1021
            &vec![1, 2],
1022
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1023
            true,
1024
            Some(10),
1025
            || (),
1026
            || (),
1027
        )
1028
        .unwrap();
1029
        assert_eq!(g.node_count(), 3);
1030
        assert_eq!(g.edge_count(), 6);
1031
        for (u, v) in [(1, 1), (1, 2), (2, 1), (2, 2), (0, 1), (0, 2)] {
1032
            assert_eq!(g.contains_edge(u.into(), v.into()), true);
1033
        }
1034
        assert_eq!(g.contains_edge(1.into(), 0.into()), false);
1035
        assert_eq!(g.contains_edge(2.into(), 0.into()), false);
1036
    }
1037

1038
    #[test]
1039
    fn test_sbm_undirected_complete_blocks_loops() {
1040
        let g = sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1041
            &vec![1, 2],
1042
            &ndarray::arr2(&[[0., 1.], [1., 1.]]).view(),
1043
            true,
1044
            Some(10),
1045
            || (),
1046
            || (),
1047
        )
1048
        .unwrap();
1049
        assert_eq!(g.node_count(), 3);
1050
        assert_eq!(g.edge_count(), 5);
1051
        for (u, v) in [(1, 1), (1, 2), (2, 2), (0, 1), (0, 2)] {
1052
            assert_eq!(g.contains_edge(u.into(), v.into()), true);
1053
        }
1054
        assert_eq!(g.contains_edge(0.into(), 0.into()), false);
1055
    }
1056

1057
    #[test]
1058
    fn test_sbm_directed_complete_blocks_noloops() {
1059
        let g = sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1060
            &vec![1, 2],
1061
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1062
            false,
1063
            Some(10),
1064
            || (),
1065
            || (),
1066
        )
1067
        .unwrap();
1068
        assert_eq!(g.node_count(), 3);
1069
        assert_eq!(g.edge_count(), 4);
1070
        for (u, v) in [(1, 2), (2, 1), (0, 1), (0, 2)] {
1071
            assert_eq!(g.contains_edge(u.into(), v.into()), true);
1072
        }
1073
        assert_eq!(g.contains_edge(1.into(), 0.into()), false);
1074
        assert_eq!(g.contains_edge(2.into(), 0.into()), false);
1075
        for u in 0..2 {
1076
            assert_eq!(g.contains_edge(u.into(), u.into()), false);
1077
        }
1078
    }
1079

1080
    #[test]
1081
    fn test_sbm_undirected_complete_blocks_noloops() {
1082
        let g = sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1083
            &vec![1, 2],
1084
            &ndarray::arr2(&[[0., 1.], [1., 1.]]).view(),
1085
            false,
1086
            Some(10),
1087
            || (),
1088
            || (),
1089
        )
1090
        .unwrap();
1091
        assert_eq!(g.node_count(), 3);
1092
        assert_eq!(g.edge_count(), 3);
1093
        for (u, v) in [(1, 2), (0, 1), (0, 2)] {
1094
            assert_eq!(g.contains_edge(u.into(), v.into()), true);
1095
        }
1096
        for u in 0..2 {
1097
            assert_eq!(g.contains_edge(u.into(), u.into()), false);
1098
        }
1099
    }
1100

1101
    #[test]
1102
    fn test_sbm_bad_array_rows_error() {
1103
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1104
            &vec![1, 2],
1105
            &ndarray::arr2(&[[0., 1.], [1., 1.], [1., 1.]]).view(),
1106
            true,
1107
            Some(10),
1108
            || (),
1109
            || (),
1110
        ) {
1111
            Ok(_) => panic!("Returned a non-error"),
1112
            Err(e) => assert_eq!(e, InvalidInputError),
1113
        };
1114
    }
1115
    #[test]
1116

1117
    fn test_sbm_bad_array_cols_error() {
1118
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1119
            &vec![1, 2],
1120
            &ndarray::arr2(&[[0., 1., 1.], [1., 1., 1.]]).view(),
1121
            true,
1122
            Some(10),
1123
            || (),
1124
            || (),
1125
        ) {
1126
            Ok(_) => panic!("Returned a non-error"),
1127
            Err(e) => assert_eq!(e, InvalidInputError),
1128
        };
1129
    }
1130

1131
    #[test]
1132
    fn test_sbm_asymmetric_array_error() {
1133
        match sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1134
            &vec![1, 2],
1135
            &ndarray::arr2(&[[0., 1.], [0., 1.]]).view(),
1136
            true,
1137
            Some(10),
1138
            || (),
1139
            || (),
1140
        ) {
1141
            Ok(_) => panic!("Returned a non-error"),
1142
            Err(e) => assert_eq!(e, InvalidInputError),
1143
        };
1144
    }
1145

1146
    #[test]
1147
    fn test_sbm_invalid_probability_error() {
1148
        match sbm_random_graph::<petgraph::graph::UnGraph<(), ()>, (), _, _, ()>(
1149
            &vec![1, 2],
1150
            &ndarray::arr2(&[[0., 1.], [0., -1.]]).view(),
1151
            true,
1152
            Some(10),
1153
            || (),
1154
            || (),
1155
        ) {
1156
            Ok(_) => panic!("Returned a non-error"),
1157
            Err(e) => assert_eq!(e, InvalidInputError),
1158
        };
1159
    }
1160

1161
    #[test]
1162
    fn test_sbm_empty_error() {
1163
        match sbm_random_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1164
            &vec![],
1165
            &ndarray::arr2(&[[]]).view(),
1166
            true,
1167
            Some(10),
1168
            || (),
1169
            || (),
1170
        ) {
1171
            Ok(_) => panic!("Returned a non-error"),
1172
            Err(e) => assert_eq!(e, InvalidInputError),
1173
        };
1174
    }
1175

1176
    // Test random_geometric_graph
1177

1178
    #[test]
1179
    fn test_random_geometric_empty() {
1180
        let g: petgraph::graph::UnGraph<Vec<f64>, ()> =
1181
            random_geometric_graph(20, 0.0, 2, None, 2.0, None, || ()).unwrap();
1182
        assert_eq!(g.node_count(), 20);
1183
        assert_eq!(g.edge_count(), 0);
1184
    }
1185

1186
    #[test]
1187
    fn test_random_geometric_complete() {
1188
        let g: petgraph::graph::UnGraph<Vec<f64>, ()> =
1189
            random_geometric_graph(10, 1.42, 2, None, 2.0, None, || ()).unwrap();
1190
        assert_eq!(g.node_count(), 10);
1191
        assert_eq!(g.edge_count(), 45);
1192
    }
1193

1194
    #[test]
1195
    fn test_random_geometric_bad_num_nodes() {
1196
        match random_geometric_graph::<petgraph::graph::UnGraph<Vec<f64>, ()>, _, ()>(
1197
            0,
1198
            1.0,
1199
            2,
1200
            None,
1201
            2.0,
1202
            None,
1203
            || (),
1204
        ) {
1205
            Ok(_) => panic!("Returned a non-error"),
1206
            Err(e) => assert_eq!(e, InvalidInputError),
1207
        };
1208
    }
1209

1210
    #[test]
1211
    fn test_random_geometric_bad_pos() {
1212
        match random_geometric_graph::<petgraph::graph::UnGraph<Vec<f64>, ()>, _, ()>(
1213
            3,
1214
            0.15,
1215
            3,
1216
            Some(vec![vec![0.5, 0.5]]),
1217
            2.0,
1218
            None,
1219
            || (),
1220
        ) {
1221
            Ok(_) => panic!("Returned a non-error"),
1222
            Err(e) => assert_eq!(e, InvalidInputError),
1223
        };
1224
    }
1225

1226
    #[test]
1227
    fn test_barabasi_albert_graph_starting_graph() {
1228
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1229
            path_graph(Some(40), None, || (), || (), false).unwrap();
1230
        let graph =
1231
            barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()).unwrap();
1232
        assert_eq!(graph.node_count(), 500);
1233
        assert_eq!(graph.edge_count(), 18439);
1234
    }
1235

1236
    #[test]
1237
    fn test_barabasi_albert_graph_invalid_starting_size() {
1238
        match barabasi_albert_graph(
1239
            5,
1240
            40,
1241
            None,
1242
            None::<petgraph::graph::UnGraph<(), ()>>,
1243
            || (),
1244
            || (),
1245
        ) {
1246
            Ok(_) => panic!("Returned a non-error"),
1247
            Err(e) => assert_eq!(e, InvalidInputError),
1248
        }
1249
    }
1250

1251
    #[test]
1252
    fn test_barabasi_albert_graph_invalid_equal_starting_size() {
1253
        match barabasi_albert_graph(
1254
            5,
1255
            5,
1256
            None,
1257
            None::<petgraph::graph::UnGraph<(), ()>>,
1258
            || (),
1259
            || (),
1260
        ) {
1261
            Ok(_) => panic!("Returned a non-error"),
1262
            Err(e) => assert_eq!(e, InvalidInputError),
1263
        }
1264
    }
1265

1266
    #[test]
1267
    fn test_barabasi_albert_graph_invalid_starting_graph() {
1268
        let starting_graph: petgraph::graph::UnGraph<(), ()> =
1269
            path_graph(Some(4), None, || (), || (), false).unwrap();
1270
        match barabasi_albert_graph(500, 40, None, Some(starting_graph), || (), || ()) {
1271
            Ok(_) => panic!("Returned a non-error"),
1272
            Err(e) => assert_eq!(e, InvalidInputError),
1273
        }
1274
    }
1275

1276
    // Test random_bipartite_graph
1277

1278
    #[test]
1279
    fn test_random_bipartite_graph_directed() {
1280
        let g: petgraph::graph::DiGraph<(), ()> =
1281
            random_bipartite_graph(10, 10, 0.5, Some(10), || (), || ()).unwrap();
1282
        assert_eq!(g.node_count(), 20);
1283
        assert_eq!(g.edge_count(), 57);
1284
    }
1285

1286
    #[test]
1287
    fn test_random_bipartite_graph_directed_empty() {
1288
        let g: petgraph::graph::DiGraph<(), ()> =
1289
            random_bipartite_graph(5, 10, 0.0, None, || (), || ()).unwrap();
1290
        assert_eq!(g.node_count(), 15);
1291
        assert_eq!(g.edge_count(), 0);
1292
    }
1293

1294
    #[test]
1295
    fn test_random_bipartite_graph_directed_complete() {
1296
        let g: petgraph::graph::DiGraph<(), ()> =
1297
            random_bipartite_graph(10, 5, 1.0, None, || (), || ()).unwrap();
1298
        assert_eq!(g.node_count(), 15);
1299
        assert_eq!(g.edge_count(), 10 * 5);
1300
    }
1301

1302
    #[test]
1303
    fn test_random_bipartite_graph_undirected() {
1304
        let g: petgraph::graph::UnGraph<(), ()> =
1305
            random_bipartite_graph(10, 10, 0.5, Some(10), || (), || ()).unwrap();
1306
        assert_eq!(g.node_count(), 20);
1307
        assert_eq!(g.edge_count(), 57);
1308
    }
1309

1310
    #[test]
1311
    fn test_random_bipartite_graph_undirected_empty() {
1312
        let g: petgraph::graph::UnGraph<(), ()> =
1313
            random_bipartite_graph(5, 10, 0.0, None, || (), || ()).unwrap();
1314
        assert_eq!(g.node_count(), 15);
1315
        assert_eq!(g.edge_count(), 0);
1316
    }
1317

1318
    #[test]
1319
    fn test_random_bipartite_graph_undirected_complete() {
1320
        let g: petgraph::graph::UnGraph<(), ()> =
1321
            random_bipartite_graph(10, 5, 1.0, None, || (), || ()).unwrap();
1322
        assert_eq!(g.node_count(), 15);
1323
        assert_eq!(g.edge_count(), 10 * 5);
1324
    }
1325

1326
    #[test]
1327
    fn test_random_bipartite_graph_error() {
1328
        match random_bipartite_graph::<petgraph::graph::DiGraph<(), ()>, (), _, _, ()>(
1329
            0,
1330
            0,
1331
            3.0,
1332
            None,
1333
            || (),
1334
            || (),
1335
        ) {
1336
            Ok(_) => panic!("Returned a non-error"),
1337
            Err(e) => assert_eq!(e, InvalidInputError),
1338
        };
1339
    }
1340

1341
    // Test hyperbolic_random_graph
1342
    //
1343
    // Hyperboloid (H^2) "polar" coordinates (r, theta) are transformed to "cartesian"
1344
    // coordinates using
1345
    // z = cosh(r)
1346
    // x = sinh(r)cos(theta)
1347
    // y = sinh(r)sin(theta)
1348

1349
    #[test]
1350
    fn test_hyperbolic_dist() {
1351
        assert_eq!(
1352
            hyperbolic_distance(&[3_f64.sinh(), 0.], &[-0.5_f64.sinh(), 0.]),
1353
            3.5
1354
        );
1355
    }
1356
    #[test]
1357
    fn test_hyperbolic_dist_inf() {
1358
        assert_eq!(
1359
            hyperbolic_distance(&[f64::INFINITY, 0.], &[0., 0.]).is_nan(),
1360
            true
1361
        );
1362
    }
1363

1364
    #[test]
1365
    fn test_hyperbolic_random_graph_seeded() {
1366
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1367
            &[
1368
                vec![3_f64.sinh(), 0.],
1369
                vec![-0.5_f64.sinh(), 0.],
1370
                vec![0.5_f64.sinh(), 0.],
1371
                vec![0., 0.],
1372
            ],
1373
            0.75,
1374
            Some(10000.),
1375
            Some(10),
1376
            || (),
1377
            || (),
1378
        )
1379
        .unwrap();
1380
        assert_eq!(g.node_count(), 4);
1381
        assert_eq!(g.edge_count(), 2);
1382
    }
1383

1384
    #[test]
1385
    fn test_hyperbolic_random_graph_threshold() {
1386
        let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1387
            &[
1388
                vec![3_f64.sinh(), 0.],
1389
                vec![-0.5_f64.sinh(), 0.],
1390
                vec![-1_f64.sinh(), 0.],
1391
            ],
1392
            1.,
1393
            None,
1394
            None,
1395
            || (),
1396
            || (),
1397
        )
1398
        .unwrap();
1399
        assert_eq!(g.node_count(), 3);
1400
        assert_eq!(g.edge_count(), 1);
1401
    }
1402

1403
    #[test]
1404
    fn test_hyperbolic_random_graph_invalid_dim_error() {
1405
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1406
            &[vec![0.]],
1407
            1.,
1408
            None,
1409
            None,
1410
            || (),
1411
            || (),
1412
        ) {
1413
            Ok(_) => panic!("Returned a non-error"),
1414
            Err(e) => assert_eq!(e, InvalidInputError),
1415
        }
1416
    }
1417

1418
    #[test]
1419
    fn test_hyperbolic_random_graph_neg_r_error() {
1420
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1421
            &[vec![0., 0.], vec![0., 0.]],
1422
            -1.,
1423
            None,
1424
            None,
1425
            || (),
1426
            || (),
1427
        ) {
1428
            Ok(_) => panic!("Returned a non-error"),
1429
            Err(e) => assert_eq!(e, InvalidInputError),
1430
        }
1431
    }
1432

1433
    #[test]
1434
    fn test_hyperbolic_random_graph_neg_beta_error() {
1435
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1436
            &[vec![0., 0.], vec![0., 0.]],
1437
            1.,
1438
            Some(-1.),
1439
            None,
1440
            || (),
1441
            || (),
1442
        ) {
1443
            Ok(_) => panic!("Returned a non-error"),
1444
            Err(e) => assert_eq!(e, InvalidInputError),
1445
        }
1446
    }
1447

1448
    #[test]
1449
    fn test_hyperbolic_random_graph_diff_dims_error() {
1450
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1451
            &[vec![0., 0.], vec![0., 0., 0.]],
1452
            1.,
1453
            None,
1454
            None,
1455
            || (),
1456
            || (),
1457
        ) {
1458
            Ok(_) => panic!("Returned a non-error"),
1459
            Err(e) => assert_eq!(e, InvalidInputError),
1460
        }
1461
    }
1462

1463
    #[test]
1464
    fn test_hyperbolic_random_graph_empty_error() {
1465
        match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
1466
            &[],
1467
            1.,
1468
            None,
1469
            None,
1470
            || (),
1471
            || (),
1472
        ) {
1473
            Ok(_) => panic!("Returned a non-error"),
1474
            Err(e) => assert_eq!(e, InvalidInputError),
1475
        }
1476
    }
1477

1478
    #[test]
1479
    fn test_hyperbolic_random_graph_directed_error() {
1480
        match hyperbolic_random_graph::<petgraph::graph::DiGraph<(), ()>, _, _, _, _>(
1481
            &[vec![0., 0.], vec![0., 0.]],
1482
            1.,
1483
            None,
1484
            None,
1485
            || (),
1486
            || (),
1487
        ) {
1488
            Ok(_) => panic!("Returned a non-error"),
1489
            Err(e) => assert_eq!(e, InvalidInputError),
1490
        }
1491
    }
1492
}
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