igraph/games.rs
1//! Random graph generators, a.k.a. *games* (`igraph_games.h`).
2//!
3//! igraph calls its random graph models *games*. Every generator of this
4//! module is an associated function of [`Graph`] returning a brand new
5//! `Result<Graph>` (or a small result struct when the model also produces
6//! vertex types or coordinates), while the two rewiring functions are
7//! methods mutating an existing graph in place.
8//!
9//! All the games draw their random numbers from the calling thread's default
10//! random number generator: seed it with [`rng::seed`](crate::rng::seed) to
11//! obtain reproducible graphs. Two runs with the same seed and the same
12//! arguments produce *exactly* the same graph. Every thread has its own
13//! default generator (see [`rng`](crate::rng)), so seeding is per-thread and
14//! concurrent threads never disturb each other's streams; an independent
15//! generator of a chosen [`RngType`](crate::rng::RngType) can be installed
16//! for the duration of a closure with [`Rng::scoped`](crate::rng::Rng::scoped):
17//!
18//! ```
19//! use igraph::prelude::*;
20//!
21//! let sample = || {
22//! rng::seed(42).unwrap();
23//! Graph::erdos_renyi_game_gnm(50, 100, false, EdgeTypeSw::Simple, false).unwrap()
24//! };
25//! // Same seed, same graph, even in another thread.
26//! let other_thread = std::thread::spawn(sample).join().unwrap();
27//! assert!(sample().is_same_graph(&other_thread).unwrap());
28//!
29//! // A scoped Mersenne Twister leaves the default generator untouched.
30//! let mut mt = Rng::new(RngType::Mt19937, 42).unwrap();
31//! let g = mt.scoped(|| Graph::tree_game(20, false, RandomTreeMethod::Prufer)).unwrap();
32//! assert!(g.is_tree(NeighborMode::All).unwrap());
33//! ```
34//!
35//! # Example
36//!
37//! ```
38//! use igraph::{games::BarabasiOptions, prelude::*};
39//!
40//! rng::seed(42).unwrap();
41//! // An Erdős–Rényi G(n, m) graph: 1000 vertices, 1000 edges, simple.
42//! let g = Graph::erdos_renyi_game_gnm(1000, 1000, false, EdgeTypeSw::Simple, false).unwrap();
43//! let degrees = g.degree(.., NeighborMode::All, Loops::Twice).unwrap();
44//! let mean = degrees.iter().sum::<i64>() as f64 / g.vcount() as f64;
45//! assert_eq!(mean, 2.0); // handshake lemma: 2m / n
46//!
47//! // A scale-free graph grown by preferential attachment.
48//! let ba = Graph::barabasi_game(100, &BarabasiOptions::default().with_m(2)).unwrap();
49//! assert_eq!(ba.vcount(), 100);
50//! assert_eq!(ba.ecount(), 1 + 98 * 2); // vertex 1 has only one vertex to attach to
51//! ```
52//!
53//! # Provided functionality
54//!
55//! | Model | Rust | C function |
56//! |---|---|---|
57//! | Erdős–Rényi `G(n, m)` | [`Graph::erdos_renyi_game_gnm`] | `igraph_erdos_renyi_game_gnm` |
58//! | Erdős–Rényi `G(n, p)` | [`Graph::erdos_renyi_game_gnp`] | `igraph_erdos_renyi_game_gnp` |
59//! | Independent edge assignment | [`Graph::iea_game`] | `igraph_iea_game` |
60//! | Barabási–Albert / Price | [`Graph::barabasi_game`] | `igraph_barabasi_game` |
61//! | Preferential attachment with aging | [`Graph::barabasi_aging_game`] | `igraph_barabasi_aging_game` |
62//! | Recent degree | [`Graph::recent_degree_game`] | `igraph_recent_degree_game` |
63//! | Recent degree with aging | [`Graph::recent_degree_aging_game`] | `igraph_recent_degree_aging_game` |
64//! | Growing random graph | [`Graph::growing_random_game`] | `igraph_growing_random_game` |
65//! | Prescribed degree sequence | [`Graph::degree_sequence_game`] | `igraph_degree_sequence_game` |
66//! | Random regular graph | [`Graph::k_regular_game`] | `igraph_k_regular_game` |
67//! | Static fitness | [`Graph::static_fitness_game`] | `igraph_static_fitness_game` |
68//! | Static power law | [`Graph::static_power_law_game`] | `igraph_static_power_law_game` |
69//! | Chung–Lu (expected degrees) | [`Graph::chung_lu_game`] | `igraph_chung_lu_game` |
70//! | Watts–Strogatz small world | [`Graph::watts_strogatz_game`] | `igraph_watts_strogatz_game` |
71//! | Rewire edges | [`Graph::rewire_edges`] | `igraph_rewire_edges` |
72//! | Rewire directed edge endpoints | [`Graph::rewire_directed_edges`] | `igraph_rewire_directed_edges` |
73//! | Forest fire | [`Graph::forest_fire_game`] | `igraph_forest_fire_game` |
74//! | Stochastic block model | [`Graph::sbm_game`] | `igraph_sbm_game` |
75//! | Hierarchical SBM | [`Graph::hsbm_game`], [`Graph::hsbm_list_game`] | `igraph_hsbm_game`, `igraph_hsbm_list_game` |
76//! | Preference (block model with random types) | [`Graph::preference_game`] | `igraph_preference_game` |
77//! | Asymmetric preference | [`Graph::asymmetric_preference_game`] | `igraph_asymmetric_preference_game` |
78//! | Callaway traits | [`Graph::callaway_traits_game`] | `igraph_callaway_traits_game` |
79//! | Establishment | [`Graph::establishment_game`] | `igraph_establishment_game` |
80//! | Geometric random graph | [`Graph::grg_game`] | `igraph_grg_game` |
81//! | Last citation | [`Graph::lastcit_game`] | `igraph_lastcit_game` |
82//! | Cited type | [`Graph::cited_type_game`] | `igraph_cited_type_game` |
83//! | Citing–cited type | [`Graph::citing_cited_type_game`] | `igraph_citing_cited_type_game` |
84//! | Interconnected islands | [`Graph::simple_interconnected_islands_game`] | `igraph_simple_interconnected_islands_game` |
85//! | Correlated graphs | [`Graph::correlated_game`], [`Graph::correlated_pair_game`] | `igraph_correlated_game`, `igraph_correlated_pair_game` |
86//! | Uniform random tree | [`Graph::tree_game`] | `igraph_tree_game` |
87//! | Random dot product graph | [`Graph::dot_product_game`] | `igraph_dot_product_game` |
88//!
89//! # See also
90//!
91//! - Deterministic generators (rings, lattices, trees, famous graphs, graphs
92//! realizing a degree sequence) live in [`constructors`](crate::constructors),
93//! e.g. [`Graph::famous`], [`Graph::square_lattice`],
94//! [`Graph::realize_degree_sequence`].
95//! - Random *bipartite* graphs: [`Graph::bipartite_game_gnp`],
96//! [`Graph::bipartite_game_gnm`], [`Graph::bipartite_iea_game`]; graphs from
97//! a hierarchical random graph model: [`Graph::hrg_game`].
98//! - Degree-preserving randomization of an existing graph: [`Graph::rewire`];
99//! testing whether a degree sequence is realizable at all:
100//! [`is_graphical`](crate::mixing::is_graphical).
101//! - Random spatial graphs from given points (the deterministic counterpart
102//! of [`Graph::grg_game`]): [`Graph::nearest_neighbor_graph`].
103//! - Measuring what the models produce: [`Graph::average_path_length`],
104//! [`Graph::transitivity_avglocal_undirected`], [`Graph::maxdegree`],
105//! [`power_law_fit`](crate::misc::power_law_fit),
106//! [`Graph::community_multilevel`] (e.g. to recover the blocks of an
107//! [`sbm_game`](Graph::sbm_game)).
108//!
109//! # Allowed edge types
110//!
111//! Several generators take an `allowed_edge_types` argument (the C type
112//! `igraph_edge_type_sw_t`) controlling whether self-loops and multi-edges may
113//! be created. It accepts both the shared [`EdgeTypeSw`] enum (simple graphs,
114//! loops *or* multi-edges) and the [`AllowedEdgeTypes`] flag set (defined in
115//! [`constants`](crate::constants) and re-exported here; the graphicality
116//! tests of [`mixing`](crate::mixing) use the very same type), which can also
117//! express *loops and multi-edges* together:
118//!
119//! ```
120//! use igraph::{games::AllowedEdgeTypes, prelude::*};
121//!
122//! rng::seed(7).unwrap();
123//! let any = AllowedEdgeTypes::LOOPS | AllowedEdgeTypes::MULTI;
124//! assert_eq!(EdgeTypeSw::Loops | EdgeTypeSw::Multi, any);
125//! let g = Graph::erdos_renyi_game_gnm(3, 50, false, any, false).unwrap();
126//! assert_eq!(g.ecount(), 50); // impossible without multi-edges on 3 vertices
127//! assert!(g.has_multiple().unwrap());
128//! ```
129
130use crate::{
131 constants::*,
132 error::{Error, Result},
133 ffi::*,
134 graph::Graph,
135 igraph_call,
136 list::{MatrixList, VectorList},
137 matrix::Matrix,
138 vector::{Vector, VectorInt},
139};
140
141// ---------------------------------------------------------------------------
142// Helpers
143// ---------------------------------------------------------------------------
144
145/// Converts a count to `igraph_int_t`, failing on overflow.
146fn int(n: usize, what: &str) -> Result<igraph_int_t> {
147 igraph_int_t::try_from(n).map_err(|_| Error::invalid(format!("{what} is too large: {n}")))
148}
149
150/// Converts a vertex count to `igraph_int_t`, requiring at most
151/// `IGRAPH_VCOUNT_MAX` (`i64::MAX - 1`) vertices.
152///
153/// Several games compute `n + 1` (or `n / bins + 1`) without overflow checks,
154/// which is undefined behaviour in C for `n == i64::MAX`.
155fn vertex_count(n: usize) -> Result<igraph_int_t> {
156 let n = int(n, "number of vertices")?;
157 if n > IGRAPH_VCOUNT_MAX_I64 {
158 return Err(Error::invalid(format!(
159 "number of vertices is too large: {n}"
160 )));
161 }
162 Ok(n)
163}
164
165/// `IGRAPH_VCOUNT_MAX` (a macro, not exported by bindgen).
166const IGRAPH_VCOUNT_MAX_I64: igraph_int_t = igraph_int_t::MAX - 1;
167/// `IGRAPH_ECOUNT_MAX` (a macro, not exported by bindgen).
168const IGRAPH_ECOUNT_MAX_I64: igraph_int_t = igraph_int_t::MAX / 2;
169/// `IGRAPH_MAX_EXACT_REAL`: integers up to 2^53 are exact as doubles.
170const MAX_EXACT_REAL: igraph_int_t = 1 << 53;
171
172/// Converts an edge count to `igraph_int_t`, requiring at most
173/// `IGRAPH_ECOUNT_MAX` (`i64::MAX / 2`) edges: some games compute
174/// `2 * num_edges` without overflow checks.
175fn edge_count(m: usize) -> Result<igraph_int_t> {
176 let m = int(m, "number of edges")?;
177 if m > IGRAPH_ECOUNT_MAX_I64 {
178 return Err(Error::new(
179 crate::error::ErrorKind::Overflow,
180 format!("number of edges is too large: {m}"),
181 ));
182 }
183 Ok(m)
184}
185
186/// Requires the (floating point) sum of `values` to be finite: igraph samples
187/// from cumulative sums, and an infinite total makes rejection samplers loop
188/// forever or produce NaN probabilities.
189fn finite_sum(values: &[f64], what: &str) -> Result<f64> {
190 let sum: f64 = values.iter().sum();
191 if sum.is_finite() {
192 Ok(sum)
193 } else {
194 Err(Error::invalid(format!("the sum of {what} must be finite")))
195 }
196}
197
198/// Error for integer arithmetic on sizes that igraph would overflow.
199fn overflow(what: &str) -> Error {
200 Error::new(
201 crate::error::ErrorKind::Overflow,
202 format!("{what} overflows a 64-bit integer"),
203 )
204}
205
206/// Vertex count of the hierarchical SBM games: igraph converts the doubles
207/// `round(rho[i] * m)` back to integers, which is exact (and defined) only
208/// below 2^53.
209fn hsbm_vertex_count(n: usize) -> Result<igraph_int_t> {
210 let n = int(n, "number of vertices")?;
211 if n >= MAX_EXACT_REAL {
212 return Err(Error::invalid(format!(
213 "number of vertices must be below 2^53 for the HSBM, got {n}"
214 )));
215 }
216 Ok(n)
217}
218
219/// Pointer to an optional viewed input vector (null when absent).
220fn opt_ptr<V>(v: &Option<crate::vector::View<'_, V>>) -> *const V {
221 v.as_ref().map_or(std::ptr::null(), |v| v.as_ptr())
222}
223
224/// Rejects NaN parameters.
225///
226/// igraph checks its real parameters with comparisons such as
227/// `p < 0 || p > 1`, which NaN passes. Several games then loop forever
228/// (NaN rewiring probabilities) or abort the process (a NaN island
229/// probability), so NaN is rejected on the Rust side.
230fn not_nan(x: f64, what: &str) -> Result<()> {
231 if x.is_nan() {
232 Err(Error::invalid(format!("{what} must not be NaN")))
233 } else {
234 Ok(())
235 }
236}
237
238/// Rejects NaN entries of a matrix (see [`not_nan`]).
239fn no_nan_entries(m: &Matrix, what: &str) -> Result<()> {
240 if m.as_slice().iter().any(|x| x.is_nan()) {
241 Err(Error::invalid(format!("{what} must not contain NaN")))
242 } else {
243 Ok(())
244 }
245}
246
247/// Requires every value to be finite and non-negative.
248///
249/// igraph samples from cumulative sums of these values: infinite entries
250/// make several games loop forever, and some checks miss negative values.
251fn finite_non_negative(values: &[f64], what: &str) -> Result<()> {
252 match values.iter().find(|x| !(x.is_finite() && **x >= 0.0)) {
253 Some(x) => Err(Error::invalid(format!(
254 "{what} must be finite and non-negative, got {x}"
255 ))),
256 None => Ok(()),
257 }
258}
259
260/// Requires a sampling distribution (finite, non-negative weights) with at
261/// least one positive weight.
262///
263/// With only zero weights igraph computes the type index `-1` and indexes out
264/// of bounds (an assertion failure that aborts the process).
265fn distribution(values: &[f64], what: &str) -> Result<()> {
266 finite_non_negative(values, what)?;
267 if values.iter().all(|&x| x == 0.0) {
268 return Err(Error::invalid(format!(
269 "{what} must contain at least one positive value"
270 )));
271 }
272 Ok(())
273}
274
275/// igraph divides by zero (`SIGFPE`) when asked to move an endpoint to a
276/// vertex other than the fixed one in a single-vertex graph: reject it.
277fn check_rewirable(graph: &Graph, prob: f64, loops: bool) -> Result<()> {
278 not_nan(prob, "the rewiring probability")?;
279 if !loops && prob > 0.0 && graph.vcount() == 1 && graph.ecount() > 0 {
280 return Err(Error::invalid(
281 "cannot rewire the edges of a single-vertex graph without allowing self-loops",
282 ));
283 }
284 Ok(())
285}
286
287// ---------------------------------------------------------------------------
288// Allowed edge types
289// ---------------------------------------------------------------------------
290
291/// Which non-simple edges a random generator may create, as a set of flags
292/// (the C type `igraph_edge_type_sw_t`).
293///
294/// This is [`constants::AllowedEdgeTypes`](crate::constants::AllowedEdgeTypes)
295/// (also re-exported as `mixing::AllowedEdgeTypes` and
296/// `constructors::AllowedEdgeTypes`), re-exported here because most
297/// generators of this module take it: see there for its constants and
298/// conversions. Every generator argument named
299/// `allowed_edge_types` accepts anything convertible into it, i.e. an
300/// [`EdgeTypeSw`] or a combination such as `EdgeTypeSw::Loops | EdgeTypeSw::Multi`.
301pub use crate::constants::AllowedEdgeTypes;
302
303fn sw(allowed: impl Into<AllowedEdgeTypes>) -> igraph_edge_type_sw_t {
304 allowed.into().to_raw()
305}
306
307// ---------------------------------------------------------------------------
308// Result structs and option structs
309// ---------------------------------------------------------------------------
310
311/// A random graph whose vertices carry a *type* (category), as produced by
312/// the type-based games ([`Graph::preference_game`],
313/// [`Graph::callaway_traits_game`], [`Graph::establishment_game`]).
314#[derive(Debug, Clone, PartialEq)]
315pub struct TypedGraph {
316 /// The generated graph.
317 pub graph: Graph,
318 /// `types[v]` is the type of vertex `v`, numbered from zero.
319 pub types: Vec<i64>,
320}
321
322/// Result of [`Graph::asymmetric_preference_game`]: every vertex has an
323/// *out-type* and an *in-type*.
324#[derive(Debug, Clone, PartialEq)]
325pub struct AsymmetricTypedGraph {
326 /// The generated (directed) graph.
327 pub graph: Graph,
328 /// `out_types[v]` is the outgoing type of vertex `v`.
329 pub out_types: Vec<i64>,
330 /// `in_types[v]` is the incoming type of vertex `v`.
331 pub in_types: Vec<i64>,
332}
333
334/// Result of [`Graph::grg_game`]: a geometric random graph together with the
335/// positions of its vertices in the unit square.
336#[derive(Debug, Clone, PartialEq)]
337pub struct GeometricGraph {
338 /// The generated graph.
339 pub graph: Graph,
340 /// x coordinates of the vertices (sorted increasingly: vertex ids follow
341 /// the x order).
342 pub x: Vec<f64>,
343 /// y coordinates of the vertices.
344 pub y: Vec<f64>,
345}
346
347/// Options of [`Graph::barabasi_game`], with the defaults of the classic
348/// undirected Barabási–Albert model (`m = 1`, `power = 1`, `A = 1`,
349/// [`BarabasiAlgorithm::Psumtree`]).
350///
351/// ```
352/// use igraph::prelude::*;
353/// use igraph::games::BarabasiOptions;
354///
355/// let opts = BarabasiOptions::default().with_m(3).with_directed(true).with_power(0.5);
356/// assert_eq!((opts.m, opts.directed, opts.power), (3, true, 0.5));
357/// ```
358#[derive(Debug, Clone)]
359pub struct BarabasiOptions<'a> {
360 /// Exponent of the preferential attachment: the probability of citing a
361 /// vertex of degree `d` is proportional to `d^power + a` (default `1`).
362 pub power: f64,
363 /// Number of edges added with each new vertex, used when `outseq` is
364 /// `None` (default `1`).
365 pub m: usize,
366 /// Explicit number of edges added with each vertex (the first entry is
367 /// ignored, as the first vertex cannot cite anybody). With `start_from`,
368 /// it refers only to the newly added vertices. Default `None`.
369 pub outseq: Option<&'a [i64]>,
370 /// Whether the out-degree also counts in the attractiveness (i.e. total
371 /// degree instead of in-degree). Ignored (assumed `true`) for undirected
372 /// graphs. Default `false`.
373 pub outpref: bool,
374 /// The constant attractiveness `A` of vertices (default `1`).
375 pub a: f64,
376 /// Whether to create a directed graph (default `false`).
377 pub directed: bool,
378 /// The sampling algorithm (default [`BarabasiAlgorithm::Psumtree`], that
379 /// creates simple graphs).
380 pub algorithm: BarabasiAlgorithm,
381 /// An optional non-empty starting graph; by default the process starts
382 /// from a single vertex (the C documentation mentions a clique of size
383 /// `m`, but the implementation starts from one vertex in igraph 1.0.0 and
384 /// 1.0.1). The generated graph contains it as its first vertices and
385 /// edges.
386 pub start_from: Option<&'a Graph>,
387}
388
389impl Default for BarabasiOptions<'_> {
390 fn default() -> Self {
391 Self {
392 power: 1.0,
393 m: 1,
394 outseq: None,
395 outpref: false,
396 a: 1.0,
397 directed: false,
398 algorithm: BarabasiAlgorithm::Psumtree,
399 start_from: None,
400 }
401 }
402}
403
404impl<'a> BarabasiOptions<'a> {
405 /// Sets [`power`](Self::power).
406 pub fn with_power(mut self, power: f64) -> Self {
407 self.power = power;
408 self
409 }
410 /// Sets [`m`](Self::m).
411 pub fn with_m(mut self, m: usize) -> Self {
412 self.m = m;
413 self
414 }
415 /// Sets [`outseq`](Self::outseq).
416 pub fn with_outseq(mut self, outseq: &'a [i64]) -> Self {
417 self.outseq = Some(outseq);
418 self
419 }
420 /// Sets [`outpref`](Self::outpref).
421 pub fn with_outpref(mut self, outpref: bool) -> Self {
422 self.outpref = outpref;
423 self
424 }
425 /// Sets the constant attractiveness [`a`](Self::a).
426 pub fn with_a(mut self, a: f64) -> Self {
427 self.a = a;
428 self
429 }
430 /// Sets [`directed`](Self::directed).
431 pub fn with_directed(mut self, directed: bool) -> Self {
432 self.directed = directed;
433 self
434 }
435 /// Sets [`algorithm`](Self::algorithm).
436 pub fn with_algorithm(mut self, algorithm: BarabasiAlgorithm) -> Self {
437 self.algorithm = algorithm;
438 self
439 }
440 /// Sets [`start_from`](Self::start_from).
441 pub fn with_start_from(mut self, start_from: &'a Graph) -> Self {
442 self.start_from = Some(start_from);
443 self
444 }
445}
446
447/// Options of [`Graph::barabasi_aging_game`].
448///
449/// The attractiveness of a vertex with (in-)degree `k` and age `l` is
450/// `(deg_coef * k^pa_exp + zero_deg_appeal) * (age_coef * l^aging_exp + zero_age_appeal)`.
451/// The defaults (those of R igraph's `sample_pa_age`) describe linear
452/// preferential attachment without aging.
453#[derive(Debug, Clone)]
454pub struct BarabasiAgingOptions<'a> {
455 /// Edges added per time step when `outseq` is `None` (default `1`).
456 pub m: usize,
457 /// Edges added in each time step (overrides `m`). Default `None`.
458 pub outseq: Option<&'a [i64]>,
459 /// Whether edges initiated by a vertex count in its attractiveness
460 /// (default `false`).
461 pub outpref: bool,
462 /// Preferential attachment exponent (default `1`).
463 pub pa_exp: f64,
464 /// Aging exponent, usually negative (default `0`, i.e. no aging).
465 pub aging_exp: f64,
466 /// Number of age bins (default `300`).
467 pub aging_bins: usize,
468 /// Degree dependent attractiveness of zero-degree vertices (default `1`).
469 pub zero_deg_appeal: f64,
470 /// Age dependent attractiveness of age-zero vertices (default `0`).
471 pub zero_age_appeal: f64,
472 /// Coefficient of the degree term (default `1`).
473 pub deg_coef: f64,
474 /// Coefficient of the age term (default `1`).
475 pub age_coef: f64,
476 /// Whether to create a directed graph (default `true`).
477 pub directed: bool,
478}
479
480impl Default for BarabasiAgingOptions<'_> {
481 fn default() -> Self {
482 Self {
483 m: 1,
484 outseq: None,
485 outpref: false,
486 pa_exp: 1.0,
487 aging_exp: 0.0,
488 aging_bins: 300,
489 zero_deg_appeal: 1.0,
490 zero_age_appeal: 0.0,
491 deg_coef: 1.0,
492 age_coef: 1.0,
493 directed: true,
494 }
495 }
496}
497
498/// Options of [`Graph::recent_degree_game`].
499///
500/// The probability that a vertex is cited is proportional to
501/// `k^power + zero_appeal`, where `k` is the number of edges it gained in the
502/// last `window` time steps.
503#[derive(Debug, Clone)]
504pub struct RecentDegreeOptions<'a> {
505 /// Exponent of the recent degree (default `1`).
506 pub power: f64,
507 /// Size of the time window (default `1`).
508 pub window: usize,
509 /// Edges added per time step when `outseq` is `None` (default `1`).
510 pub m: usize,
511 /// Edges added in each time step (overrides `m`). Default `None`.
512 pub outseq: Option<&'a [i64]>,
513 /// Whether edges originated by a vertex also count as recent edges
514 /// (default `false`).
515 pub outpref: bool,
516 /// Attractiveness of the vertices without recent edges (default `1`).
517 pub zero_appeal: f64,
518 /// Whether to create a directed graph (default `false`).
519 pub directed: bool,
520}
521
522impl Default for RecentDegreeOptions<'_> {
523 fn default() -> Self {
524 Self {
525 power: 1.0,
526 window: 1,
527 m: 1,
528 outseq: None,
529 outpref: false,
530 zero_appeal: 1.0,
531 directed: false,
532 }
533 }
534}
535
536/// Options of [`Graph::recent_degree_aging_game`].
537///
538/// The attractiveness is `(k^pa_exp + zero_appeal) * l^aging_exp`, where `k`
539/// is the number of edges gained in the last `window` steps and `l` the age.
540#[derive(Debug, Clone)]
541pub struct RecentDegreeAgingOptions<'a> {
542 /// Edges added per time step when `outseq` is `None` (default `1`).
543 pub m: usize,
544 /// Edges added in each time step (overrides `m`). Default `None`.
545 pub outseq: Option<&'a [i64]>,
546 /// Whether edges initiated by a vertex are counted (default `false`).
547 pub outpref: bool,
548 /// Preferential attachment exponent (default `1`).
549 pub pa_exp: f64,
550 /// Aging exponent, usually negative (default `0`, i.e. no aging).
551 pub aging_exp: f64,
552 /// Number of age bins (default `300`).
553 pub aging_bins: usize,
554 /// Size of the time window (default `1`).
555 pub window: usize,
556 /// Degree dependent attractiveness of vertices without recent edges
557 /// (default `1`).
558 pub zero_appeal: f64,
559 /// Whether to create a directed graph (default `false`).
560 pub directed: bool,
561}
562
563impl Default for RecentDegreeAgingOptions<'_> {
564 fn default() -> Self {
565 Self {
566 m: 1,
567 outseq: None,
568 outpref: false,
569 pa_exp: 1.0,
570 aging_exp: 0.0,
571 aging_bins: 300,
572 window: 1,
573 zero_appeal: 1.0,
574 directed: false,
575 }
576 }
577}
578
579// ---------------------------------------------------------------------------
580// The games
581// ---------------------------------------------------------------------------
582
583impl igraph_t {
584 /// Generates a uniformly random graph with a fixed number of vertices and
585 /// edges: the Erdős–Rényi `G(n, m)` model.
586 ///
587 /// Among all graphs on `num_vertices` vertices with exactly `num_edges`
588 /// edges (and respecting `allowed_edge_types`, which accepts an
589 /// [`EdgeTypeSw`] or an [`AllowedEdgeTypes`]), one is drawn uniformly at
590 /// random. With `edge_labeled = true` the sampling is uniform over
591 /// *ordered edge lists* instead (see [`Graph::iea_game`]); pass `false` for
592 /// the classic model.
593 ///
594 /// Time complexity: O(|V| + |E|).
595 ///
596 /// See also [`Graph::erdos_renyi_game_gnp`] (independent edges) and
597 /// [`Graph::bipartite_game_gnm`] (the bipartite analogue).
598 ///
599 /// Binds [`igraph_erdos_renyi_game_gnm`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_erdos_renyi_game_gnm).
600 ///
601 /// # Errors
602 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
603 /// `num_edges` is larger than the number of available vertex pairs (for
604 /// simple graphs).
605 ///
606 /// # Examples
607 /// ```
608 /// use igraph::prelude::*;
609 ///
610 /// rng::seed(42).unwrap();
611 /// let g = Graph::erdos_renyi_game_gnm(10, 20, true, EdgeTypeSw::Simple, false).unwrap();
612 /// assert_eq!((g.vcount(), g.ecount(), g.is_directed()), (10, 20, true));
613 /// // Only 45 vertex pairs exist in a simple undirected graph on 10 vertices.
614 /// let err = Graph::erdos_renyi_game_gnm(10, 46, false, EdgeTypeSw::Simple, false).unwrap_err();
615 /// assert_eq!(err.kind(), ErrorKind::InvalidValue);
616 /// ```
617 pub fn erdos_renyi_game_gnm(
618 num_vertices: usize,
619 num_edges: usize,
620 directed: bool,
621 mode: impl Into<AllowedEdgeTypes>,
622 edge_labeled: bool,
623 ) -> Result<Graph> {
624 let n = int(num_vertices, "number of vertices")?;
625 let m = int(num_edges, "number of edges")?;
626 let mode = sw(mode);
627 Graph::init_with(|g| unsafe {
628 igraph_erdos_renyi_game_gnm(g, n, m, directed, mode, edge_labeled)
629 })
630 }
631
632 /// Generates a random graph where every vertex pair is connected
633 /// independently with probability `p`: the Erdős–Rényi `G(n, p)` (or
634 /// Gilbert) model.
635 ///
636 /// When multi-edges are allowed, `p` is the *expected number* of edges
637 /// between any vertex pair (multiplicities are geometric:
638 /// `P(m edges) = q (1 - q)^m` with `q = 1 / (1 + p)`). The expected mean
639 /// degree is `p (n - 1)` without self-loops, `p (n + 1)` for undirected
640 /// graphs with self-loops and `p n` for directed ones with self-loops; set
641 /// `p = k / n` for a mean degree of about `k`. `edge_labeled = true`
642 /// samples uniformly from ordered edge lists instead; igraph implements
643 /// this variant only when multi-edges are allowed. Use `false` for the
644 /// classic model.
645 ///
646 /// Time complexity: O(|V| + |E|).
647 ///
648 /// See also [`Graph::erdos_renyi_game_gnm`] (fixed edge count),
649 /// [`Graph::bipartite_game_gnp`] and [`Graph::mean_degree`].
650 ///
651 /// Binds [`igraph_erdos_renyi_game_gnp`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_erdos_renyi_game_gnp).
652 ///
653 /// # Errors
654 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `p` is
655 /// not in `[0, 1]` for graphs without multi-edges (or is negative for
656 /// multigraphs), or is NaN or infinite, or if `num_vertices` exceeds
657 /// igraph's maximum vertex count `i64::MAX - 1` (checked on the Rust
658 /// side: igraph 1.0.1 overflows computing `n + 1` for loopy undirected
659 /// graphs, and mishandles an infinite `p`);
660 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) if, with
661 /// `edge_labeled = true`, the expected number of edges exceeds 2^56
662 /// (igraph 1.0.1 would overflow doubling the sampled edge count);
663 /// [`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented) for
664 /// `edge_labeled = true` without multi-edges.
665 ///
666 /// # Examples
667 /// ```
668 /// use igraph::prelude::*;
669 ///
670 /// // p = 1 gives the complete graph, p = 0 the empty graph.
671 /// let k5 = Graph::erdos_renyi_game_gnp(5, 1.0, false, EdgeTypeSw::Simple, false).unwrap();
672 /// assert!(k5.is_same_graph(&Graph::full(5, false, false).unwrap()).unwrap());
673 /// let e5 = Graph::erdos_renyi_game_gnp(5, 0.0, false, EdgeTypeSw::Simple, false).unwrap();
674 /// assert_eq!(e5.ecount(), 0);
675 ///
676 /// // Mean degree p (n - 1) ≈ 4.
677 /// rng::seed(42).unwrap();
678 /// let g = Graph::erdos_renyi_game_gnp(2001, 0.002, false, EdgeTypeSw::Simple, false).unwrap();
679 /// assert!((g.mean_degree(true).unwrap() - 4.0).abs() < 0.3);
680 /// ```
681 pub fn erdos_renyi_game_gnp(
682 num_vertices: usize,
683 p: f64,
684 directed: bool,
685 allowed_edge_types: impl Into<AllowedEdgeTypes>,
686 edge_labeled: bool,
687 ) -> Result<Graph> {
688 let n = vertex_count(num_vertices)?;
689 not_nan(p, "the edge probability")?;
690 let allowed = allowed_edge_types.into();
691 // With multi-edges igraph accepts any `p >= 0`; an infinite one gives
692 // NaN sampling parameters (silently the empty graph, after converting
693 // NaN to an integer in the edge-labeled variant).
694 if p.is_infinite() {
695 return Err(Error::invalid(
696 "the expected edge multiplicity `p` must be finite",
697 ));
698 }
699 if edge_labeled && allowed.multi {
700 // igraph draws the edge count from a geometric distribution
701 // with mean `max_edges * p` and computes `2 * count` unchecked:
702 // keep the mean far below `IGRAPH_ECOUNT_MAX` (such a graph
703 // could not be allocated anyway).
704 let nf = n as f64;
705 let max_edges = match (directed, allowed.loops) {
706 (true, true) => nf * nf,
707 (true, false) => nf * (nf - 1.0),
708 (false, true) => nf * (nf + 1.0) / 2.0,
709 (false, false) => nf * (nf - 1.0) / 2.0,
710 };
711 if max_edges * p > (IGRAPH_ECOUNT_MAX_I64 >> 6) as f64 {
712 return Err(Error::new(
713 crate::error::ErrorKind::Overflow,
714 format!(
715 "the expected number of edges ({:e}) is too large",
716 max_edges * p
717 ),
718 ));
719 }
720 }
721 let mode = allowed.to_raw();
722 Graph::init_with(|g| unsafe {
723 igraph_erdos_renyi_game_gnp(g, n, p, directed, mode, edge_labeled)
724 })
725 }
726
727 /// Generates a random multigraph by *independent edge assignment* (IEA):
728 /// each of the `num_edges` edges is assigned to a uniformly random ordered
729 /// vertex pair, independently of the others.
730 ///
731 /// This is uniform sampling of *edge-labeled* graphs: all simple graphs
732 /// have the same probability, while multigraphs are down-weighted by the
733 /// factorials of their edge multiplicities. `loops` controls whether
734 /// self-loops can be produced. This function is marked *experimental* in
735 /// igraph.
736 ///
737 /// Time complexity: O(|V| + |E|).
738 ///
739 /// See also [`Graph::erdos_renyi_game_gnm`] with `edge_labeled = true`
740 /// and [`Graph::bipartite_iea_game`].
741 ///
742 /// Binds [`igraph_iea_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_iea_game).
743 ///
744 /// # Examples
745 /// ```
746 /// use igraph::prelude::*;
747 ///
748 /// rng::seed(1).unwrap();
749 /// // Two vertices, 10 edges, no loops: all edges are parallel.
750 /// let g = Graph::iea_game(2, 10, false, false).unwrap();
751 /// assert!(!g.has_loop().unwrap());
752 /// assert_eq!(g.count_multiple(..).unwrap(), vec![10; 10]);
753 /// ```
754 pub fn iea_game(
755 num_vertices: usize,
756 num_edges: usize,
757 directed: bool,
758 loops: bool,
759 ) -> Result<Graph> {
760 let n = int(num_vertices, "number of vertices")?;
761 let m = int(num_edges, "number of edges")?;
762 Graph::init_with(|g| unsafe { igraph_iea_game(g, n, m, directed, loops) })
763 }
764
765 /// Generates a graph by preferential attachment: the Barabási–Albert
766 /// model and its variants (Price model, non-linear attachment).
767 ///
768 /// Starting from a single vertex (or from
769 /// [`start_from`](BarabasiOptions::start_from)), vertices are added one at
770 /// a time; each new vertex cites `m` (or `outseq[i]`) existing vertices,
771 /// chosen with probability proportional to `d^power + A`, where `d` is the
772 /// in-degree (or total degree with `outpref`, and always in undirected
773 /// graphs). See [`BarabasiOptions`] for all the parameters and
774 /// [`BarabasiAlgorithm`] for the sampling algorithms: `Bag` only supports
775 /// `power = 1, A = 1` and may create multi-edges, `Psumtree` creates simple
776 /// graphs, `PsumtreeMultiple` allows multi-edges.
777 ///
778 /// Time complexity: O(|V| + |E|).
779 ///
780 /// See also [`Graph::barabasi_aging_game`] and
781 /// [`Graph::recent_degree_game`] for variants of preferential attachment,
782 /// and [`power_law_fit`](crate::misc::power_law_fit) to estimate the
783 /// exponent of the resulting degree distribution.
784 ///
785 /// Binds [`igraph_barabasi_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_barabasi_game).
786 ///
787 /// # Errors
788 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for a
789 /// non-positive `A` (negative `A` when the total degree is used), negative
790 /// `outseq` entries or an `outseq` whose length differs from the number
791 /// of new vertices, an empty starting graph or one with more than
792 /// `num_vertices` vertices, `power != 1` or `A != 1` with
793 /// [`BarabasiAlgorithm::Bag`], or an undirected starting graph for a
794 /// directed result without `outpref`.
795 ///
796 /// # Examples
797 /// ```
798 /// use igraph::prelude::*;
799 /// use igraph::games::BarabasiOptions;
800 ///
801 /// rng::seed(42).unwrap();
802 /// let g = Graph::barabasi_game(1000, &BarabasiOptions::default().with_m(3)).unwrap();
803 /// // Vertex 1 cites vertex 0 once, vertex 2 cites 0 and 1, then 3 edges each.
804 /// assert_eq!(g.ecount(), 1 + 2 + 997 * 3);
805 /// // Hubs emerge: the maximum degree is far above the mean (about 6).
806 /// let max = g.maxdegree(.., NeighborMode::All, Loops::Twice).unwrap();
807 /// assert!(max > 30);
808 /// ```
809 pub fn barabasi_game(num_vertices: usize, options: &BarabasiOptions<'_>) -> Result<Graph> {
810 let n = int(num_vertices, "number of vertices")?;
811 let m = int(options.m, "m")?;
812 let outseq = options.outseq.map(VectorInt::view);
813 let start = options
814 .start_from
815 .map_or(std::ptr::null(), |g| g as *const Graph);
816 Graph::init_with(|g| unsafe {
817 igraph_barabasi_game(
818 g,
819 n,
820 options.power,
821 m,
822 opt_ptr(&outseq),
823 options.outpref,
824 options.a,
825 options.directed,
826 options.algorithm.into(),
827 start,
828 )
829 })
830 }
831
832 /// Generates a graph by preferential attachment with *aging* of vertices.
833 ///
834 /// Starting from one vertex, a new vertex is added in each step and
835 /// connected to `m` existing ones, chosen with probability proportional
836 /// to `(deg_coef * k^pa_exp + zero_deg_appeal) * (age_coef * l^aging_exp + zero_age_appeal)`,
837 /// where `k` is the (in-)degree and `l` the age of the vertex; the age is
838 /// incremented every `floor(n / aging_bins) + 1` steps. See
839 /// [`BarabasiAgingOptions`].
840 ///
841 /// Time complexity: O((|V| + |V|/aging_bins) log |V| + |E|).
842 ///
843 /// See also [`Graph::recent_degree_aging_game`], where only recently
844 /// gained edges count.
845 ///
846 /// Binds [`igraph_barabasi_aging_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_barabasi_aging_game).
847 ///
848 /// # Errors
849 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for zero
850 /// `aging_bins`, negative appeal or coefficient terms, or an `outseq`
851 /// whose length differs from the number of vertices.
852 ///
853 /// # Examples
854 /// ```
855 /// use igraph::prelude::*;
856 /// use igraph::games::BarabasiAgingOptions;
857 ///
858 /// rng::seed(3).unwrap();
859 /// let opts = BarabasiAgingOptions { m: 2, aging_exp: -1.0, aging_bins: 10, ..Default::default() };
860 /// let g = Graph::barabasi_aging_game(50, &opts).unwrap();
861 /// assert_eq!(g.vcount(), 50);
862 /// assert_eq!(g.ecount(), 49 * 2);
863 ///
864 /// // From igraph's unit tests: a very steep aging preference for young
865 /// // vertices makes every vertex cite its predecessor twice.
866 /// let young = BarabasiAgingOptions {
867 /// m: 2, pa_exp: 0.0, aging_exp: -10.0, aging_bins: 6,
868 /// zero_deg_appeal: 0.1, deg_coef: 0.1, ..Default::default()
869 /// };
870 /// let line = Graph::barabasi_aging_game(5, &young).unwrap();
871 /// let mut edges = line.edge_list();
872 /// edges.sort();
873 /// assert_eq!(edges, [(1, 0), (1, 0), (2, 1), (2, 1), (3, 2), (3, 2), (4, 3), (4, 3)]);
874 /// ```
875 pub fn barabasi_aging_game(
876 num_vertices: usize,
877 options: &BarabasiAgingOptions<'_>,
878 ) -> Result<Graph> {
879 let n = int(num_vertices, "number of vertices")?;
880 let m = int(options.m, "m")?;
881 let bins = int(options.aging_bins, "aging_bins")?;
882 let outseq = options.outseq.map(VectorInt::view);
883 let o = options;
884 Graph::init_with(|g| unsafe {
885 igraph_barabasi_aging_game(
886 g,
887 n,
888 m,
889 opt_ptr(&outseq),
890 o.outpref,
891 o.pa_exp,
892 o.aging_exp,
893 bins,
894 o.zero_deg_appeal,
895 o.zero_age_appeal,
896 o.deg_coef,
897 o.age_coef,
898 o.directed,
899 )
900 })
901 }
902
903 /// Generates a growing graph where the attractiveness of a vertex depends
904 /// on the number of edges it gained *recently*.
905 ///
906 /// In each of the `num_vertices` time steps a vertex is added, citing `m`
907 /// (or `outseq[i]`) existing vertices chosen with probability proportional
908 /// to `k^power + zero_appeal`, where `k` counts the edges gained in the
909 /// last `window` steps. See [`RecentDegreeOptions`].
910 ///
911 /// Time complexity: O(|V| log |V| + |E|).
912 ///
913 /// See also [`Graph::barabasi_game`] (the whole degree counts) and
914 /// [`Graph::recent_degree_aging_game`].
915 ///
916 /// Binds [`igraph_recent_degree_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_recent_degree_game).
917 ///
918 /// # Errors
919 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for a
920 /// negative `zero_appeal`, or an `outseq` whose length differs from the
921 /// number of vertices. A `window` longer than `num_vertices` is
922 /// equivalent to `num_vertices` (no edge ever leaves it) and is clamped
923 /// on the Rust side, since igraph 1.0.1 sizes its history queue from it
924 /// without overflow checks.
925 ///
926 /// # Examples
927 /// ```
928 /// use igraph::{games::RecentDegreeOptions, prelude::*};
929 ///
930 /// // From igraph's unit tests: a strong preference for recently cited
931 /// // vertices makes a star of double edges around vertex 0.
932 /// let opts = RecentDegreeOptions {
933 /// power: 30.0, window: 100, m: 2, zero_appeal: 0.001, directed: true,
934 /// ..Default::default()
935 /// };
936 /// let g = Graph::recent_degree_game(10, &opts).unwrap();
937 /// assert_eq!(g.ecount(), 18);
938 /// assert!(g.edge_list().iter().all(|&(_, to)| to == 0));
939 /// ```
940 pub fn recent_degree_game(
941 num_vertices: usize,
942 options: &RecentDegreeOptions<'_>,
943 ) -> Result<Graph> {
944 let n = vertex_count(num_vertices)?;
945 let m = int(options.m, "m")?;
946 // A window at least as long as the whole process is equivalent to
947 // `window = n` (no edge ever leaves it); clamping keeps igraph's
948 // history capacity `1.5 * window * |E| / n + 10` in range.
949 let window = int(options.window.min(num_vertices), "window")?;
950 let outseq = options.outseq.map(VectorInt::view);
951 let o = options;
952 Graph::init_with(|g| unsafe {
953 igraph_recent_degree_game(
954 g,
955 n,
956 o.power,
957 window,
958 m,
959 opt_ptr(&outseq),
960 o.outpref,
961 o.zero_appeal,
962 o.directed,
963 )
964 })
965 }
966
967 /// Preferential attachment based on the number of edges gained recently,
968 /// with aging of vertices.
969 ///
970 /// Like [`Graph::barabasi_aging_game`], but the degree part counts only
971 /// the edges gained in the last `window` steps: the attractiveness is
972 /// `(k^pa_exp + zero_appeal) * l^aging_exp`. See
973 /// [`RecentDegreeAgingOptions`].
974 ///
975 /// Time complexity: O((|V| + |V|/aging_bins) log |V| + |E|).
976 ///
977 /// Binds [`igraph_recent_degree_aging_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_recent_degree_aging_game).
978 ///
979 /// # Errors
980 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for zero
981 /// `aging_bins`, a negative `zero_appeal`, or an `outseq` whose length
982 /// differs from the number of vertices. As in
983 /// [`Graph::recent_degree_game`], a `window` longer than `num_vertices`
984 /// is clamped to it (an equivalent model).
985 ///
986 /// # Examples
987 /// ```
988 /// use igraph::{games::RecentDegreeAgingOptions, prelude::*};
989 ///
990 /// // From igraph's unit tests: one citation per step yields a tree.
991 /// let opts = RecentDegreeAgingOptions {
992 /// outpref: true, pa_exp: 2.0, aging_exp: 2.0, aging_bins: 5, window: 4,
993 /// ..Default::default()
994 /// };
995 /// let g = Graph::recent_degree_aging_game(10, &opts).unwrap();
996 /// assert!(g.is_tree(NeighborMode::All).unwrap());
997 /// ```
998 pub fn recent_degree_aging_game(
999 num_vertices: usize,
1000 options: &RecentDegreeAgingOptions<'_>,
1001 ) -> Result<Graph> {
1002 let n = vertex_count(num_vertices)?;
1003 let m = int(options.m, "m")?;
1004 let bins = int(options.aging_bins, "aging_bins")?;
1005 // See `recent_degree_game`: windows longer than the process are
1006 // equivalent to `window = n`.
1007 let window = int(options.window.min(num_vertices), "window")?;
1008 let outseq = options.outseq.map(VectorInt::view);
1009 let o = options;
1010 Graph::init_with(|g| unsafe {
1011 igraph_recent_degree_aging_game(
1012 g,
1013 n,
1014 m,
1015 opt_ptr(&outseq),
1016 o.outpref,
1017 o.pa_exp,
1018 o.aging_exp,
1019 bins,
1020 window,
1021 o.zero_appeal,
1022 o.directed,
1023 )
1024 })
1025 }
1026
1027 /// Generates a growing random graph.
1028 ///
1029 /// Starting with one vertex, in each step a new vertex and `m` new edges
1030 /// are added. The endpoints of the edges are uniformly random vertices,
1031 /// unless `citation` is `true`, in which case every edge goes from the
1032 /// newest vertex to a uniformly chosen older one. Such graphs differ from
1033 /// non-growing random graphs (older vertices have higher degree). The
1034 /// result may contain multi-edges (and self-loops without `citation`).
1035 ///
1036 /// Time complexity: O(|V| + |E|).
1037 ///
1038 /// See also [`Graph::barabasi_game`] (growth with preferential instead of
1039 /// uniform attachment).
1040 ///
1041 /// Binds [`igraph_growing_random_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_growing_random_game).
1042 ///
1043 /// # Examples
1044 /// ```
1045 /// use igraph::prelude::*;
1046 ///
1047 /// rng::seed(5).unwrap();
1048 /// let g = Graph::growing_random_game(10, 2, true, true).unwrap();
1049 /// assert_eq!(g.ecount(), 9 * 2);
1050 /// // Citations always point back in time.
1051 /// assert!(g.edge_list().iter().all(|&(from, to)| from > to));
1052 /// ```
1053 pub fn growing_random_game(
1054 num_vertices: usize,
1055 m: usize,
1056 directed: bool,
1057 citation: bool,
1058 ) -> Result<Graph> {
1059 let n = int(num_vertices, "number of vertices")?;
1060 let m = int(m, "m")?;
1061 Graph::init_with(|g| unsafe { igraph_growing_random_game(g, n, m, directed, citation) })
1062 }
1063
1064 /// Generates a random graph with a prescribed degree sequence.
1065 ///
1066 /// `out_degrees` is the degree sequence of an undirected graph (when
1067 /// `in_degrees` is `None`), or the out-degree sequence of a directed one.
1068 /// The [`DegreeSequenceMethod`] chooses the sampler:
1069 ///
1070 /// - `Configuration`: the configuration model; may create loops and
1071 /// multi-edges.
1072 /// - `ConfigurationSimple`: configuration model with rejection of
1073 /// non-simple results; uniform over simple graphs (can be slow).
1074 /// - `FastHeurSimple`: simple graphs, fast but not uniform.
1075 /// - `EdgeSwitchingSimple`: MCMC with degree-preserving edge switches;
1076 /// simple graphs.
1077 /// - `VigerLatapy`: undirected *connected* simple graphs, approximately
1078 /// uniform.
1079 ///
1080 /// Time complexity: O(|V| + |E|) for `Configuration` and
1081 /// `EdgeSwitchingSimple`, unknown for the others.
1082 ///
1083 /// See also [`Graph::realize_degree_sequence`] (a deterministic
1084 /// realization), [`is_graphical`](crate::mixing::is_graphical) (is the
1085 /// sequence realizable at all?) and [`Graph::rewire`] (degree-preserving
1086 /// randomization of an existing graph).
1087 ///
1088 /// Binds [`igraph_degree_sequence_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_degree_sequence_game).
1089 ///
1090 /// # Errors
1091 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
1092 /// sequences are not realizable with the chosen method (e.g. odd degree
1093 /// sum, mismatched in/out sums or lengths, non-graphical sequence for the
1094 /// simple methods).
1095 ///
1096 /// # Examples
1097 /// ```
1098 /// use igraph::prelude::*;
1099 ///
1100 /// rng::seed(42).unwrap();
1101 /// let degrees = [3, 3, 2, 2, 2, 1, 1];
1102 /// let g = Graph::degree_sequence_game(°rees, None, DegreeSequenceMethod::VigerLatapy).unwrap();
1103 /// assert_eq!(g.degree(.., NeighborMode::All, Loops::Twice).unwrap(), degrees);
1104 /// // The Viger–Latapy sampler produces connected simple graphs.
1105 /// assert!(g.is_simple(true).unwrap());
1106 /// assert!(g.is_connected(Connectedness::Weak).unwrap());
1107 /// ```
1108 pub fn degree_sequence_game(
1109 out_degrees: &[i64],
1110 in_degrees: Option<&[i64]>,
1111 method: DegreeSequenceMethod,
1112 ) -> Result<Graph> {
1113 let out = VectorInt::view(out_degrees);
1114 let inn = in_degrees.map(VectorInt::view);
1115 Graph::init_with(|g| unsafe {
1116 igraph_degree_sequence_game(g, out.as_ptr(), opt_ptr(&inn), method.into())
1117 })
1118 }
1119
1120 /// Generates a random `k`-regular graph: every vertex has degree `k` (or
1121 /// out- and in-degree `k` in the directed case).
1122 ///
1123 /// For undirected graphs, `num_vertices * k` must be even. With
1124 /// `multiple = false` the result is simple. The sampling is not uniform
1125 /// (it relies on [`Graph::degree_sequence_game`]).
1126 ///
1127 /// Time complexity: O(|V| + |E|) if `multiple` is `true`, unknown
1128 /// otherwise.
1129 ///
1130 /// See also [`Graph::ring`] and [`Graph::square_lattice`] for
1131 /// deterministic regular graphs.
1132 ///
1133 /// Binds [`igraph_k_regular_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_k_regular_game).
1134 ///
1135 /// # Errors
1136 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) e.g. for
1137 /// an odd number of vertices and odd `k` in an undirected graph.
1138 ///
1139 /// # Examples
1140 /// ```
1141 /// use igraph::prelude::*;
1142 ///
1143 /// rng::seed(42).unwrap();
1144 /// let g = Graph::k_regular_game(10, 3, false, false).unwrap();
1145 /// assert_eq!(g.degree(.., NeighborMode::All, Loops::Twice).unwrap(), vec![3; 10]);
1146 /// assert!(Graph::k_regular_game(9, 3, false, false).is_err());
1147 /// ```
1148 pub fn k_regular_game(
1149 num_vertices: usize,
1150 k: usize,
1151 directed: bool,
1152 multiple: bool,
1153 ) -> Result<Graph> {
1154 let n = int(num_vertices, "number of vertices")?;
1155 let k = int(k, "k")?;
1156 Graph::init_with(|g| unsafe { igraph_k_regular_game(g, n, k, directed, multiple) })
1157 }
1158
1159 /// Generates a non-growing random graph with edge probabilities
1160 /// proportional to vertex *fitness* scores.
1161 ///
1162 /// The graph has `fitness_out.len()` vertices and exactly `num_edges`
1163 /// edges. Pairs `(i, j)` are drawn with probabilities proportional to the
1164 /// fitnesses (out-fitness of `i` and in-fitness of `j` for directed
1165 /// graphs, which are created when `fitness_in` is given) and connected
1166 /// unless forbidden by `allowed_edge_types`. The expected degrees are
1167 /// proportional to the fitnesses (exactly so when loops and multi-edges
1168 /// are allowed). This is the model of Goh, Kahng and Kim (2001).
1169 ///
1170 /// Time complexity: O(|V| + |E| log |E|).
1171 ///
1172 /// See also [`Graph::static_power_law_game`] (power-law fitnesses) and
1173 /// [`Graph::chung_lu_game`] (independent edges with prescribed expected
1174 /// degrees).
1175 ///
1176 /// Binds [`igraph_static_fitness_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_static_fitness_game).
1177 ///
1178 /// # Errors
1179 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1180 /// negative or non-finite fitnesses, mismatched lengths, requesting edges
1181 /// when every fitness is zero, or more edges than the non-zero fitnesses
1182 /// allow in a graph without multi-edges. The fitnesses are validated on
1183 /// the Rust side: igraph 1.0.1 accepts a negative out- or in-fitness in
1184 /// the directed case (as long as the other vector is non-negative) and
1185 /// then creates edges to non-existent vertices, and loops forever on
1186 /// infinite or NaN fitnesses, or on finite ones whose sum is infinite
1187 /// (also rejected).
1188 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) for more than
1189 /// `i64::MAX / 2` edges (igraph 1.0.1 would overflow computing
1190 /// `2 * num_edges` and abort).
1191 ///
1192 /// # Examples
1193 /// ```
1194 /// use igraph::prelude::*;
1195 ///
1196 /// rng::seed(42).unwrap();
1197 /// // Vertex 3 has zero fitness: it stays isolated.
1198 /// let g = Graph::static_fitness_game(3, &[1.0, 2.0, 3.0, 0.0], None, EdgeTypeSw::Simple).unwrap();
1199 /// assert_eq!(g.ecount(), 3);
1200 /// assert_eq!(g.degree_of(3, NeighborMode::All, Loops::Twice).unwrap(), 0);
1201 /// ```
1202 pub fn static_fitness_game(
1203 num_edges: usize,
1204 fitness_out: &[f64],
1205 fitness_in: Option<&[f64]>,
1206 allowed_edge_types: impl Into<AllowedEdgeTypes>,
1207 ) -> Result<Graph> {
1208 let m = edge_count(num_edges)?;
1209 finite_non_negative(fitness_out, "the out-fitness scores")?;
1210 finite_sum(fitness_out, "the out-fitness scores")?;
1211 if let Some(fi) = fitness_in {
1212 finite_non_negative(fi, "the in-fitness scores")?;
1213 finite_sum(fi, "the in-fitness scores")?;
1214 }
1215 let fo = Vector::view(fitness_out);
1216 let fi = fitness_in.map(Vector::view);
1217 let mode = sw(allowed_edge_types);
1218 Graph::init_with(|g| unsafe {
1219 igraph_static_fitness_game(g, m, fo.as_ptr(), opt_ptr(&fi), mode)
1220 })
1221 }
1222
1223 /// Generates a non-growing random graph with expected power-law degree
1224 /// distributions.
1225 ///
1226 /// Uses [`Graph::static_fitness_game`] with fitnesses `i^(-alpha)` for
1227 /// `i = 1, ..., n`, where `alpha = 1 / (gamma - 1)` and `gamma` is the
1228 /// exponent: vertex `v` (counting from zero) gets `(n - v)^(-alpha)`, so
1229 /// the highest-numbered vertices are the hubs (the finite size correction
1230 /// adds a constant offset to `i`). Pass
1231 /// `exponent_in = Some(gamma_in)` for a directed graph (the in-fitnesses are
1232 /// shuffled to avoid in/out correlations), `None` for an undirected one.
1233 /// Exponents must be at least 2 (`f64::INFINITY` gives an Erdős–Rényi
1234 /// graph). `finite_size_correction` applies the correction of Cho et al.
1235 /// that reduces finite size effects for exponents below 3.
1236 ///
1237 /// Time complexity: O(|V| + |E| log |E|).
1238 ///
1239 /// See also [`power_law_fit`](crate::misc::power_law_fit) to estimate the
1240 /// exponent back from the degrees.
1241 ///
1242 /// Binds [`igraph_static_power_law_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_static_power_law_game).
1243 ///
1244 /// # Errors
1245 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1246 /// exponents below 2 (or NaN), or too many edges for a simple graph;
1247 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) for more than
1248 /// `i64::MAX / 2` edges.
1249 ///
1250 /// # Examples
1251 /// ```
1252 /// use igraph::prelude::*;
1253 ///
1254 /// rng::seed(42).unwrap();
1255 /// let g = Graph::static_power_law_game(1000, 2000, 2.5, None, EdgeTypeSw::Simple, true).unwrap();
1256 /// assert_eq!((g.vcount(), g.ecount()), (1000, 2000));
1257 /// assert!(Graph::static_power_law_game(10, 10, 1.5, None, EdgeTypeSw::Simple, false).is_err());
1258 /// ```
1259 pub fn static_power_law_game(
1260 num_vertices: usize,
1261 num_edges: usize,
1262 exponent_out: f64,
1263 exponent_in: Option<f64>,
1264 allowed_edge_types: impl Into<AllowedEdgeTypes>,
1265 finite_size_correction: bool,
1266 ) -> Result<Graph> {
1267 let n = int(num_vertices, "number of vertices")?;
1268 let m = edge_count(num_edges)?;
1269 if exponent_out.is_nan() {
1270 return Err(Error::invalid("the out-degree exponent must not be NaN"));
1271 }
1272 // igraph treats any negative (or NaN) in-exponent as "undirected":
1273 // reject those explicitly so that `Some(_)` always means directed.
1274 let exponent_in = match exponent_in {
1275 Some(e) if e.is_nan() || e < 2.0 => {
1276 return Err(Error::invalid(format!(
1277 "the in-degree exponent must be at least 2, got {e}"
1278 )));
1279 }
1280 Some(e) => e,
1281 None => -1.0,
1282 };
1283 let mode = sw(allowed_edge_types);
1284 Graph::init_with(|g| unsafe {
1285 igraph_static_power_law_game(
1286 g,
1287 n,
1288 m,
1289 exponent_out,
1290 exponent_in,
1291 mode,
1292 finite_size_correction,
1293 )
1294 })
1295 }
1296
1297 /// Samples a graph from the Chung–Lu model, i.e. with prescribed
1298 /// *expected* degrees.
1299 ///
1300 /// Each pair `i, j` is connected independently with a probability
1301 /// depending on `q_ij = w_i w_j / S`, where `w` are the vertex weights
1302 /// (out-weights `out_weights` and in-weights `in_weights` in the directed
1303 /// case, created when `in_weights` is given) and `S` is their sum. The
1304 /// [`ChungLuVariant`] selects `p_ij = min(q_ij, 1)` (`Original`, where the
1305 /// expected degrees equal the weights when loops are allowed),
1306 /// `q_ij / (1 + q_ij)` (`MaxEnt`) or `1 - exp(-q_ij)` (`Nr`). `loops`
1307 /// controls whether self-loops may be created. Experimental in igraph.
1308 ///
1309 /// Time complexity: O(|V| + |E|).
1310 ///
1311 /// See also [`Graph::static_fitness_game`] (fixed number of edges) and
1312 /// [`Graph::degree_sequence_game`] (exact degrees).
1313 ///
1314 /// Binds [`igraph_chung_lu_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_chung_lu_game).
1315 ///
1316 /// # Errors
1317 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1318 /// negative or non-finite weights, or in/out weights with different
1319 /// lengths or sums. Also, on the Rust side, when the sum of the weights
1320 /// is infinite or the product of the largest out- and in-weight
1321 /// overflows: igraph 1.0.1 would compute NaN probabilities and loop
1322 /// forever.
1323 ///
1324 /// # Examples
1325 /// ```
1326 /// use igraph::prelude::*;
1327 ///
1328 /// rng::seed(42).unwrap();
1329 /// let weights = vec![2.0; 500];
1330 /// let g = Graph::chung_lu_game(&weights, None, true, ChungLuVariant::Original).unwrap();
1331 /// // With loops allowed, the expected degrees are exactly the weights.
1332 /// assert!((g.mean_degree(true).unwrap() - 2.0).abs() < 0.3);
1333 /// ```
1334 pub fn chung_lu_game(
1335 out_weights: &[f64],
1336 in_weights: Option<&[f64]>,
1337 loops: bool,
1338 variant: ChungLuVariant,
1339 ) -> Result<Graph> {
1340 // igraph checks each weight, but neither their sum nor the products
1341 // `w_i w_j`: an infinite one gives NaN connection probabilities, and
1342 // then a NaN geometric gap converted to an integer (UB, endless loop).
1343 finite_non_negative(out_weights, "the out-weights")?;
1344 finite_sum(out_weights, "the out-weights")?;
1345 let max = |w: &[f64]| w.iter().copied().fold(0.0, f64::max);
1346 let (max_out, max_in) = match in_weights {
1347 Some(w) => {
1348 finite_non_negative(w, "the in-weights")?;
1349 finite_sum(w, "the in-weights")?;
1350 (max(out_weights), max(w))
1351 }
1352 None => (max(out_weights), max(out_weights)),
1353 };
1354 if !(max_out * max_in).is_finite() {
1355 return Err(Error::invalid(
1356 "the weights are too large: the product of the largest out- and in-weight overflows",
1357 ));
1358 }
1359 let wo = Vector::view(out_weights);
1360 let wi = in_weights.map(Vector::view);
1361 Graph::init_with(|g| unsafe {
1362 igraph_chung_lu_game(g, wo.as_ptr(), opt_ptr(&wi), loops, variant.into())
1363 })
1364 }
1365
1366 /// Generates a Watts–Strogatz small-world graph.
1367 ///
1368 /// A periodic `dim`-dimensional lattice with `size` vertices along each
1369 /// dimension is built, every vertex is connected to its neighbors within
1370 /// `nei` steps, and then *both* endpoints of each edge are rewired with
1371 /// probability `p` (as in [`Graph::rewire_edges`]). Note that this
1372 /// differs from the original model, which rewires one endpoint only: for
1373 /// `p = 1` the result is a `G(n, m)` graph.
1374 ///
1375 /// Time complexity: O(|V| d^o + |E|), `d` average degree, `o = nei`.
1376 ///
1377 /// See also [`Graph::square_lattice`] (the unrewired lattice),
1378 /// [`Graph::average_path_length`] and
1379 /// [`Graph::transitivity_avglocal_undirected`] (the two quantities of the
1380 /// small-world phenomenon).
1381 ///
1382 /// Binds [`igraph_watts_strogatz_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_watts_strogatz_game).
1383 ///
1384 /// # Errors
1385 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `dim`
1386 /// or `size` is zero, or `p` is not in `[0, 1]` (NaN included).
1387 ///
1388 /// # Examples
1389 /// ```
1390 /// use igraph::prelude::*;
1391 ///
1392 /// rng::seed(42).unwrap();
1393 /// // A ring of 100 vertices, each linked to 2 neighbors on each side.
1394 /// let g = Graph::watts_strogatz_game(1, 100, 2, 0.05, EdgeTypeSw::Simple).unwrap();
1395 /// assert_eq!((g.vcount(), g.ecount()), (100, 200));
1396 /// // Without rewiring, the 1-dimensional lattice with nei = 1 is a cycle.
1397 /// let c = Graph::watts_strogatz_game(1, 10, 1, 0.0, EdgeTypeSw::Simple).unwrap();
1398 /// assert!(c.isomorphic(&Graph::ring(10, false, false, true).unwrap()).unwrap());
1399 /// ```
1400 pub fn watts_strogatz_game(
1401 dim: usize,
1402 size: usize,
1403 nei: usize,
1404 p: f64,
1405 allowed_edge_types: impl Into<AllowedEdgeTypes>,
1406 ) -> Result<Graph> {
1407 let dim = int(dim, "dim")?;
1408 let size = int(size, "size")?;
1409 let nei = int(nei, "nei")?;
1410 not_nan(p, "the rewiring probability")?;
1411 let mode = sw(allowed_edge_types);
1412 Graph::init_with(|g| unsafe { igraph_watts_strogatz_game(g, dim, size, nei, p, mode) })
1413 }
1414
1415 /// Rewires the edges of the graph in place, with constant probability.
1416 ///
1417 /// Each endpoint of each edge is moved to a uniformly random vertex with
1418 /// probability `prob` (in `[0, 1]`), respecting `allowed_edge_types`. The
1419 /// number of vertices and edges is preserved (and the directedness).
1420 ///
1421 /// Time complexity: O(|V| + |E|).
1422 ///
1423 /// Unlike [`Graph::rewire`], this does *not* preserve the degrees.
1424 ///
1425 /// Binds [`igraph_rewire_edges`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_rewire_edges).
1426 ///
1427 /// # Errors
1428 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `prob`
1429 /// is not in `[0, 1]` (NaN included), or if `prob > 0` and the graph has a
1430 /// single vertex and some edges but self-loops are not allowed (there is
1431 /// no other vertex to move an endpoint to; igraph 1.0.1 crashes with a
1432 /// division by zero there, so the Rust side rejects it).
1433 ///
1434 /// # Examples
1435 /// ```
1436 /// use igraph::prelude::*;
1437 ///
1438 /// rng::seed(42).unwrap();
1439 /// let mut g = Graph::ring(4, false, false, true).unwrap();
1440 /// let before = g.edge_list();
1441 /// g.rewire_edges(0.0, EdgeTypeSw::Simple).unwrap(); // probability 0: no change
1442 /// assert_eq!(g.edge_list(), before);
1443 /// g.rewire_edges(1.0, EdgeTypeSw::Simple).unwrap();
1444 /// assert_eq!(g.ecount(), 4);
1445 /// assert!(g.is_simple(true).unwrap());
1446 /// ```
1447 pub fn rewire_edges(
1448 &mut self,
1449 prob: f64,
1450 allowed_edge_types: impl Into<AllowedEdgeTypes>,
1451 ) -> Result<()> {
1452 let allowed = allowed_edge_types.into();
1453 check_rewirable(self, prob, allowed.loops)?;
1454 igraph_call!(igraph_rewire_edges(self, prob, allowed.to_raw()))
1455 }
1456
1457 /// Rewires one chosen endpoint of the directed edges in place, with
1458 /// constant probability.
1459 ///
1460 /// With `mode = Out` the *target* of each edge is rewired (the out-degree
1461 /// sequence is preserved), with `mode = In` the *source* (the in-degree
1462 /// sequence is preserved); `mode = All` and undirected graphs fall back to
1463 /// [`Graph::rewire_edges`]. `loops` allows self-loops. The result may
1464 /// contain multi-edges.
1465 ///
1466 /// Time complexity: O(|E|).
1467 ///
1468 /// Binds [`igraph_rewire_directed_edges`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_rewire_directed_edges).
1469 ///
1470 /// # Errors
1471 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `prob`
1472 /// is not in `[0, 1]` (NaN included), or if `prob > 0`, `loops` is
1473 /// `false` and the graph has a single vertex and some edges (igraph 1.0.1
1474 /// crashes with a division by zero there, so the Rust side rejects it).
1475 ///
1476 /// # Examples
1477 /// ```
1478 /// use igraph::prelude::*;
1479 ///
1480 /// rng::seed(42).unwrap();
1481 /// let mut g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2), (3, 0)], 4, true).unwrap();
1482 /// let outdeg = g.degree(.., NeighborMode::Out, Loops::Twice).unwrap();
1483 /// g.rewire_directed_edges(1.0, false, NeighborMode::Out).unwrap();
1484 /// assert_eq!(g.degree(.., NeighborMode::Out, Loops::Twice).unwrap(), outdeg);
1485 /// ```
1486 pub fn rewire_directed_edges(
1487 &mut self,
1488 prob: f64,
1489 loops: bool,
1490 mode: NeighborMode,
1491 ) -> Result<()> {
1492 check_rewirable(self, prob, loops)?;
1493 igraph_call!(igraph_rewire_directed_edges(self, prob, loops, mode.into()))
1494 }
1495
1496 /// Generates a growing network with the *forest fire* model of Leskovec,
1497 /// Kleinberg and Faloutsos.
1498 ///
1499 /// Each new vertex cites `ambs` uniformly chosen *ambassadors*; then, for
1500 /// each cited vertex `v`, it "burns" (cites) a geometrically distributed
1501 /// number of `v`'s not-yet-cited out-neighbors (mean `p / (1 - p)`,
1502 /// `p = fw_prob`) and in-neighbors (backward probability
1503 /// `bw_factor * fw_prob`), recursively. The model reproduces heavy tailed
1504 /// degrees, community structure, densification and shrinking diameters.
1505 ///
1506 /// Time complexity: TODO in igraph (roughly proportional to the number of
1507 /// burned vertices).
1508 ///
1509 /// Binds [`igraph_forest_fire_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_forest_fire_game).
1510 ///
1511 /// # Errors
1512 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) unless
1513 /// `0 <= fw_prob < 1` and `0 <= bw_factor * fw_prob < 1` (NaN values are
1514 /// rejected on the Rust side, igraph would silently accept them).
1515 ///
1516 /// # Examples
1517 /// ```
1518 /// use igraph::prelude::*;
1519 ///
1520 /// // With no burning and more ambassadors than vertices, every new vertex
1521 /// // cites all the previous ones: a transitive tournament.
1522 /// let g = Graph::forest_fire_game(5, 0.0, 0.0, 100, true).unwrap();
1523 /// assert_eq!(g.ecount(), 10);
1524 /// assert!(g.is_dag().unwrap());
1525 /// ```
1526 pub fn forest_fire_game(
1527 num_vertices: usize,
1528 fw_prob: f64,
1529 bw_factor: f64,
1530 ambs: usize,
1531 directed: bool,
1532 ) -> Result<Graph> {
1533 let n = int(num_vertices, "number of vertices")?;
1534 let ambs = int(ambs, "ambs")?;
1535 not_nan(fw_prob, "the forward burning probability")?;
1536 // Also catches `bw_factor = ±inf` with `fw_prob = 0`.
1537 not_nan(bw_factor * fw_prob, "the backward burning probability")?;
1538 Graph::init_with(|g| unsafe {
1539 igraph_forest_fire_game(g, n, fw_prob, bw_factor, ambs, directed)
1540 })
1541 }
1542
1543 /// Samples a graph from a stochastic block model (SBM).
1544 ///
1545 /// Vertices are split into consecutive blocks of sizes `block_sizes`
1546 /// (vertex ids follow the block order); a vertex of block `i` and one of
1547 /// block `j` are connected with probability `pref_matrix[(i, j)]` (the
1548 /// expected edge multiplicity when multi-edges are allowed). The
1549 /// preference matrix must be square, and symmetric for undirected graphs.
1550 ///
1551 /// Time complexity: O(|V| + |E| + k²), `k` the number of blocks.
1552 ///
1553 /// See also [`Graph::hsbm_game`] (hierarchical version),
1554 /// [`Graph::preference_game`] (random block assignment) and community
1555 /// detection, e.g. [`Graph::community_multilevel`], to recover the blocks.
1556 ///
1557 /// Binds [`igraph_sbm_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_sbm_game).
1558 ///
1559 /// # Errors
1560 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for a
1561 /// non-square or (undirected) non-symmetric matrix, entries out of range
1562 /// (probabilities in `[0, 1]`, or non-negative expected multiplicities
1563 /// with multi-edges) or NaN, or negative block sizes or a number of
1564 /// blocks not matching the matrix. With multi-edges, expected
1565 /// multiplicities that are infinite or above about 9e15 are also
1566 /// rejected on the Rust side (igraph 1.0.1 silently returns no edges for
1567 /// the former and loops forever on the latter);
1568 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) if the block sizes
1569 /// overflow when summed (checked on the Rust side, before igraph sums
1570 /// them unchecked).
1571 ///
1572 /// # Examples
1573 /// ```
1574 /// use igraph::prelude::*;
1575 ///
1576 /// rng::seed(42).unwrap();
1577 /// // Two cliques of 3 vertices, no edges in between.
1578 /// let pref = Matrix::from_rows(&[[1.0, 0.0], [0.0, 1.0]]).unwrap();
1579 /// let g = Graph::sbm_game(&pref, &[3, 3], false, EdgeTypeSw::Simple).unwrap();
1580 /// assert_eq!(g.ecount(), 6);
1581 /// assert!(g.edge_list().iter().all(|&(a, b)| (a < 3) == (b < 3)));
1582 /// ```
1583 pub fn sbm_game(
1584 pref_matrix: &Matrix,
1585 block_sizes: &[i64],
1586 directed: bool,
1587 allowed_edge_types: impl Into<AllowedEdgeTypes>,
1588 ) -> Result<Graph> {
1589 no_nan_entries(pref_matrix, "the preference matrix")?;
1590 let allowed = allowed_edge_types.into();
1591 // With multi-edges igraph samples gaps from a geometric distribution
1592 // with success probability `x / (1 + x)`: for infinite `x` that is
1593 // NaN (silently no edges), and when it rounds to 1 (`x` above about
1594 // 9e15) the gaps are all zero and igraph loops forever.
1595 if allowed.multi
1596 && let Some(x) = pref_matrix
1597 .as_slice()
1598 .iter()
1599 .find(|&&x| !x.is_finite() || x / (1.0 + x) >= 1.0)
1600 {
1601 return Err(Error::invalid(format!(
1602 "expected edge multiplicities must be finite and below 9e15, got {x}"
1603 )));
1604 }
1605 // igraph sums the block sizes before validating them.
1606 if let Some(b) = block_sizes.iter().find(|&&b| b < 0) {
1607 return Err(Error::invalid(format!(
1608 "block sizes must be non-negative, got {b}"
1609 )));
1610 }
1611 block_sizes
1612 .iter()
1613 .try_fold(0 as igraph_int_t, |acc, &b| acc.checked_add(b))
1614 .ok_or_else(|| overflow("the sum of the block sizes"))?;
1615 let sizes = VectorInt::view(block_sizes);
1616 let mode = allowed.to_raw();
1617 Graph::init_with(|g| unsafe {
1618 igraph_sbm_game(g, pref_matrix, sizes.as_ptr(), directed, mode)
1619 })
1620 }
1621
1622 /// Samples an undirected graph from the *hierarchical* stochastic block
1623 /// model.
1624 ///
1625 /// The `num_vertices` vertices are split into blocks of `block_size`
1626 /// vertices (`num_vertices / block_size` must be an integer). Within each
1627 /// block, vertices form clusters with sizes given by the fractions `rho`
1628 /// (summing to 1, with `rho[i] * block_size` integral), and two vertices
1629 /// of clusters `i` and `j` of the same block are connected with
1630 /// probability `c[(i, j)]` (`c` square and symmetric). Vertices of
1631 /// different blocks are connected with probability `p`.
1632 ///
1633 /// Binds [`igraph_hsbm_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_hsbm_game).
1634 ///
1635 /// # Errors
1636 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
1637 /// `num_vertices` or `block_size` is zero, `block_size` does not divide
1638 /// `num_vertices`, `rho` does not sum to one or gives non-integral
1639 /// cluster sizes, `c` is not a symmetric `rho.len() × rho.len()` matrix
1640 /// of probabilities, or `p` is not in `[0, 1]` (NaN values are rejected
1641 /// on the Rust side, igraph would silently accept them), or
1642 /// `num_vertices` is 2^53 or more (cluster sizes are computed in
1643 /// floating point).
1644 ///
1645 /// # Examples
1646 /// ```
1647 /// use igraph::prelude::*;
1648 ///
1649 /// // One block, clusters of 6 and 4 vertices, only inter-cluster edges:
1650 /// // the complete bipartite graph K(6, 4).
1651 /// let c = Matrix::from_rows(&[[0.0, 1.0], [1.0, 0.0]]).unwrap();
1652 /// let g = Graph::hsbm_game(10, 10, &[0.6, 0.4], &c, 0.0).unwrap();
1653 /// assert_eq!(g.ecount(), 24);
1654 /// ```
1655 pub fn hsbm_game(
1656 num_vertices: usize,
1657 block_size: usize,
1658 rho: &[f64],
1659 c: &Matrix,
1660 p: f64,
1661 ) -> Result<Graph> {
1662 let n = hsbm_vertex_count(num_vertices)?;
1663 let m = int(block_size, "block size")?;
1664 not_nan(p, "the inter-block probability `p`")?;
1665 no_nan_entries(c, "the cluster connection matrix `c`")?;
1666 let rho = Vector::view(rho);
1667 Graph::init_with(|g| unsafe { igraph_hsbm_game(g, n, m, rho.as_ptr(), c, p) })
1668 }
1669
1670 /// Hierarchical stochastic block model, general version with blocks of
1671 /// different shapes.
1672 ///
1673 /// Like [`Graph::hsbm_game`], but block `b` has `block_sizes[b]` vertices,
1674 /// cluster fractions `rhos[b]` and cluster connection matrix `cs[b]`.
1675 /// Vertices of different blocks are connected with probability `p`.
1676 ///
1677 /// Binds [`igraph_hsbm_list_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_hsbm_list_game).
1678 ///
1679 /// # Errors
1680 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
1681 /// three lists are empty or have different lengths, a block size is not
1682 /// positive, the sizes do not sum to `num_vertices`, a `rho`/`c` pair is
1683 /// invalid (as in [`Graph::hsbm_game`]), `p` is not in `[0, 1]`, or
1684 /// `num_vertices` or a block size is 2^53 or more;
1685 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) if the block sizes
1686 /// overflow when summed.
1687 ///
1688 /// # Examples
1689 /// ```
1690 /// use igraph::prelude::*;
1691 ///
1692 /// // A block of 3 vertices forming a triangle, and a block of 4 whose two
1693 /// // clusters of 2 are fully connected to each other: K3 + C4.
1694 /// let one = Matrix::from_rows(&[[1.0]]).unwrap();
1695 /// let cross = Matrix::from_rows(&[[0.0, 1.0], [1.0, 0.0]]).unwrap();
1696 /// let g = Graph::hsbm_list_game(7, &[3, 4], &[vec![1.0], vec![0.5, 0.5]], &[one, cross], 0.0)
1697 /// .unwrap();
1698 /// assert_eq!(g.ecount(), 3 + 4);
1699 /// assert_eq!(g.connected_components(Connectedness::Weak).unwrap().count, 2);
1700 /// ```
1701 pub fn hsbm_list_game(
1702 num_vertices: usize,
1703 block_sizes: &[i64],
1704 rhos: &[Vec<f64>],
1705 cs: &[Matrix],
1706 p: f64,
1707 ) -> Result<Graph> {
1708 let n = hsbm_vertex_count(num_vertices)?;
1709 not_nan(p, "the inter-block probability `p`")?;
1710 // igraph sums the block sizes before validating them, and a block
1711 // larger than 2^53 vertices makes `round(rho * m)` inexact or out of
1712 // range.
1713 if let Some(b) = block_sizes
1714 .iter()
1715 .find(|&&b| !(0..MAX_EXACT_REAL).contains(&b))
1716 {
1717 return Err(Error::invalid(format!(
1718 "block sizes must be non-negative and below 2^53, got {b}"
1719 )));
1720 }
1721 block_sizes
1722 .iter()
1723 .try_fold(0 as igraph_int_t, |acc, &b| acc.checked_add(b))
1724 .ok_or_else(|| overflow("the sum of the block sizes"))?;
1725 for c in cs {
1726 no_nan_entries(c, "the cluster connection matrices `cs`")?;
1727 }
1728 let mlist = VectorInt::view(block_sizes);
1729 let rholist = VectorList::from(rhos);
1730 let clist: MatrixList = cs.iter().cloned().collect();
1731 Graph::init_with(|g| unsafe {
1732 igraph_hsbm_list_game(g, n, mlist.as_ptr(), &rholist, &clist, p)
1733 })
1734 }
1735
1736 /// Generates a graph with vertex types and type-dependent connection
1737 /// preferences (a block model with random block assignment).
1738 ///
1739 /// Each of the `num_vertices` vertices gets a type drawn from `type_dist`
1740 /// (uniform when `None`), then each vertex pair is connected with
1741 /// probability `pref_matrix[(type_u, type_v)]`. The number of types is the
1742 /// size of the square `pref_matrix`, whose entries must be probabilities
1743 /// in `[0, 1]`; it must be symmetric for undirected graphs. With
1744 /// `fixed_sizes = true`, `type_dist` gives the *exact number* of vertices
1745 /// of each type (whole numbers; equal groups when `None`, the first
1746 /// `num_vertices % types` groups getting one extra vertex), and the
1747 /// vertices are assigned to the types in order. `loops` allows self-loops.
1748 ///
1749 /// Time complexity: O(|V| + |E|).
1750 ///
1751 /// See also [`Graph::sbm_game`] (fixed, consecutive blocks) and
1752 /// [`Graph::asymmetric_preference_game`].
1753 ///
1754 /// Binds [`igraph_preference_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_preference_game).
1755 ///
1756 /// # Errors
1757 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for an
1758 /// empty or non-square `pref_matrix`, entries outside `[0, 1]`, a
1759 /// non-symmetric matrix for undirected graphs, a `type_dist` of the wrong
1760 /// length or with negative or non-finite entries, a random `type_dist`
1761 /// (`fixed_sizes = false`) without any positive entry, or fixed sizes
1762 /// that are not whole numbers summing to `num_vertices`. The finiteness,
1763 /// positivity and integrality checks are done on the Rust side: igraph
1764 /// 1.0.1 loops forever on infinite weights, aborts on all-zero weights and
1765 /// leaves vertex types uninitialized for fractional sizes.
1766 ///
1767 /// # Examples
1768 /// ```
1769 /// use igraph::prelude::*;
1770 ///
1771 /// rng::seed(42).unwrap();
1772 /// // Two fixed groups of 5, connected only across groups: bipartite.
1773 /// let pref = Matrix::from_rows(&[[0.0, 1.0], [1.0, 0.0]]).unwrap();
1774 /// let r = Graph::preference_game(10, Some(&[5.0, 5.0]), true, &pref, false, false).unwrap();
1775 /// assert_eq!(r.graph.ecount(), 25);
1776 /// assert_eq!(r.types.iter().filter(|&&t| t == 0).count(), 5);
1777 /// assert!(r.graph.is_bipartite().unwrap());
1778 /// ```
1779 pub fn preference_game(
1780 num_vertices: usize,
1781 type_dist: Option<&[f64]>,
1782 fixed_sizes: bool,
1783 pref_matrix: &Matrix,
1784 directed: bool,
1785 loops: bool,
1786 ) -> Result<TypedGraph> {
1787 let n = int(num_vertices, "number of vertices")?;
1788 let types = int(pref_matrix.nrow(), "number of types")?;
1789 if let Some(td) = type_dist {
1790 if fixed_sizes {
1791 finite_non_negative(td, "the group sizes")?;
1792 if let Some(x) = td.iter().find(|x| x.fract() != 0.0) {
1793 return Err(Error::invalid(format!(
1794 "the group sizes must be whole numbers, got {x}"
1795 )));
1796 }
1797 } else {
1798 distribution(td, "the vertex type distribution")?;
1799 }
1800 }
1801 let td = type_dist.map(Vector::view);
1802 let mut node_types = VectorInt::new();
1803 let graph = Graph::init_with(|g| unsafe {
1804 igraph_preference_game(
1805 g,
1806 n,
1807 types,
1808 opt_ptr(&td),
1809 fixed_sizes,
1810 pref_matrix,
1811 &mut node_types,
1812 directed,
1813 loops,
1814 )
1815 })?;
1816 Ok(TypedGraph {
1817 graph,
1818 types: node_types.into(),
1819 })
1820 }
1821
1822 /// Generates a directed graph with asymmetric vertex types and connection
1823 /// preferences.
1824 ///
1825 /// Every vertex gets an *out-type* and an *in-type*, drawn from the joint
1826 /// distribution `type_dist_matrix` (independent uniform types when
1827 /// `None`); then each ordered pair `(u, v)` is connected with probability
1828 /// `pref_matrix[(out_type_u, in_type_v)]`. The numbers of out- and
1829 /// in-types are the numbers of rows and columns of `pref_matrix`. `loops`
1830 /// allows self-loops.
1831 ///
1832 /// Time complexity: O(|V| + |E|).
1833 ///
1834 /// Binds [`igraph_asymmetric_preference_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_asymmetric_preference_game).
1835 ///
1836 /// # Errors
1837 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for an
1838 /// empty `pref_matrix`, entries outside `[0, 1]`, or a `type_dist_matrix`
1839 /// of the wrong shape, with negative or non-finite entries or without a
1840 /// positive one (the last two are checked on the Rust side: igraph 1.0.1
1841 /// loops forever on infinite weights and aborts on all-zero ones).
1842 ///
1843 /// # Examples
1844 /// ```
1845 /// use igraph::prelude::*;
1846 ///
1847 /// rng::seed(42).unwrap();
1848 /// // Out-type 0 vertices link to every in-type 1 vertex, nothing else.
1849 /// let pref = Matrix::from_rows(&[[0.0, 1.0], [0.0, 0.0]]).unwrap();
1850 /// let r = Graph::asymmetric_preference_game(20, None, &pref, false).unwrap();
1851 /// for (u, v) in r.graph.edge_list() {
1852 /// assert_eq!((r.out_types[u as usize], r.in_types[v as usize]), (0, 1));
1853 /// }
1854 /// ```
1855 pub fn asymmetric_preference_game(
1856 num_vertices: usize,
1857 type_dist_matrix: Option<&Matrix>,
1858 pref_matrix: &Matrix,
1859 loops: bool,
1860 ) -> Result<AsymmetricTypedGraph> {
1861 let n = int(num_vertices, "number of vertices")?;
1862 let out_types = int(pref_matrix.nrow(), "number of out-types")?;
1863 let in_types = int(pref_matrix.ncol(), "number of in-types")?;
1864 if let Some(td) = type_dist_matrix {
1865 distribution(td.as_slice(), "the type distribution matrix")?;
1866 }
1867 let td = type_dist_matrix.map_or(std::ptr::null(), |m| m as *const Matrix);
1868 let mut outv = VectorInt::new();
1869 let mut inv = VectorInt::new();
1870 let graph = Graph::init_with(|g| unsafe {
1871 igraph_asymmetric_preference_game(
1872 g,
1873 n,
1874 out_types,
1875 in_types,
1876 td,
1877 pref_matrix,
1878 &mut outv,
1879 &mut inv,
1880 loops,
1881 )
1882 })?;
1883 Ok(AsymmetricTypedGraph {
1884 graph,
1885 out_types: outv.into(),
1886 in_types: inv.into(),
1887 })
1888 }
1889
1890 /// Simulates a growing network with vertex types: the model of Callaway,
1891 /// Hopcroft, Kleinberg, Newman and Strogatz.
1892 ///
1893 /// In each time step a vertex is added, with a type drawn from
1894 /// `type_dist` (uniform when `None`); then `edges_per_step` times, two
1895 /// uniformly random vertices are picked and connected with probability
1896 /// `pref_matrix[(type_u, type_v)]`. The number of types is the size of
1897 /// the square `pref_matrix`. The two endpoints are drawn independently
1898 /// (with replacement) among all vertices added so far, so the result may
1899 /// contain self-loops and multi-edges; no edges are attempted in the step
1900 /// that adds the first vertex, hence at most `(n - 1) * edges_per_step`
1901 /// edges are created.
1902 ///
1903 /// Time complexity: O(|V| k log |V|), `k = edges_per_step`.
1904 ///
1905 /// Binds [`igraph_callaway_traits_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_callaway_traits_game).
1906 ///
1907 /// # Errors
1908 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for an
1909 /// empty or non-square `pref_matrix`, entries outside `[0, 1]`, a
1910 /// non-symmetric matrix for undirected graphs, or a `type_dist` of the
1911 /// wrong length, with negative or non-finite entries or without positive
1912 /// ones (infinite weights, on which igraph 1.0.1 loops forever, are
1913 /// rejected on the Rust side).
1914 ///
1915 /// # Examples
1916 /// ```
1917 /// use igraph::prelude::*;
1918 ///
1919 /// rng::seed(42).unwrap();
1920 /// // Two types that only connect among themselves.
1921 /// let pref = Matrix::from_rows(&[[1.0, 0.0], [0.0, 1.0]]).unwrap();
1922 /// let r = Graph::callaway_traits_game(50, 2, None, &pref, false).unwrap();
1923 /// assert!(r.graph.ecount() <= 49 * 2);
1924 /// assert!(r.graph.edge_list().iter().all(|&(u, v)| r.types[u as usize] == r.types[v as usize]));
1925 /// ```
1926 pub fn callaway_traits_game(
1927 num_vertices: usize,
1928 edges_per_step: usize,
1929 type_dist: Option<&[f64]>,
1930 pref_matrix: &Matrix,
1931 directed: bool,
1932 ) -> Result<TypedGraph> {
1933 let n = int(num_vertices, "number of vertices")?;
1934 let types = int(pref_matrix.nrow(), "number of types")?;
1935 let k = int(edges_per_step, "edges_per_step")?;
1936 if let Some(td) = type_dist {
1937 distribution(td, "the vertex type distribution")?;
1938 }
1939 let td = type_dist.map(Vector::view);
1940 let mut node_types = VectorInt::new();
1941 let graph = Graph::init_with(|g| unsafe {
1942 igraph_callaway_traits_game(
1943 g,
1944 n,
1945 types,
1946 k,
1947 opt_ptr(&td),
1948 pref_matrix,
1949 directed,
1950 &mut node_types,
1951 )
1952 })?;
1953 Ok(TypedGraph {
1954 graph,
1955 types: node_types.into(),
1956 })
1957 }
1958
1959 /// Generates a graph with a simple growing model with vertex types (the
1960 /// *establishment* game).
1961 ///
1962 /// In each time step a vertex with a random type (from `type_dist`,
1963 /// uniform when `None`) is added, and it tries to connect to `k`
1964 /// uniformly chosen existing vertices; each connection succeeds with
1965 /// probability `pref_matrix[(type_new, type_old)]`. The `k` candidates
1966 /// are distinct, so the result is simple; the first `k` vertices have too
1967 /// few predecessors and make no connection attempts. Edges point from the
1968 /// new vertex to the older one.
1969 ///
1970 /// Time complexity: O(|V| k log |V|).
1971 ///
1972 /// Binds [`igraph_establishment_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_establishment_game).
1973 ///
1974 /// # Errors
1975 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for an
1976 /// empty or non-square `pref_matrix`, entries outside `[0, 1]`, a
1977 /// non-symmetric matrix for undirected graphs, or a `type_dist` of the
1978 /// wrong length, with negative or non-finite entries or without positive
1979 /// ones (infinite weights, on which igraph 1.0.1 loops forever, are
1980 /// rejected on the Rust side).
1981 ///
1982 /// # Examples
1983 /// ```
1984 /// use igraph::prelude::*;
1985 ///
1986 /// rng::seed(42).unwrap();
1987 /// // A single type whose connections always succeed: after the first
1988 /// // `k` vertices, every vertex cites exactly `k` older ones.
1989 /// let always = Matrix::from_rows(&[[1.0]]).unwrap();
1990 /// let r = Graph::establishment_game(20, 3, None, &always, true).unwrap();
1991 /// assert_eq!(r.graph.ecount(), (20 - 3) * 3);
1992 /// assert!(r.graph.is_simple(true).unwrap() && r.graph.is_dag().unwrap());
1993 /// ```
1994 pub fn establishment_game(
1995 num_vertices: usize,
1996 k: usize,
1997 type_dist: Option<&[f64]>,
1998 pref_matrix: &Matrix,
1999 directed: bool,
2000 ) -> Result<TypedGraph> {
2001 let n = int(num_vertices, "number of vertices")?;
2002 let types = int(pref_matrix.nrow(), "number of types")?;
2003 let k = int(k, "k")?;
2004 if let Some(td) = type_dist {
2005 distribution(td, "the vertex type distribution")?;
2006 }
2007 let td = type_dist.map(Vector::view);
2008 let mut node_types = VectorInt::new();
2009 let graph = Graph::init_with(|g| unsafe {
2010 igraph_establishment_game(
2011 g,
2012 n,
2013 types,
2014 k,
2015 opt_ptr(&td),
2016 pref_matrix,
2017 directed,
2018 &mut node_types,
2019 )
2020 })?;
2021 Ok(TypedGraph {
2022 graph,
2023 types: node_types.into(),
2024 })
2025 }
2026
2027 /// Generates a geometric random graph.
2028 ///
2029 /// `num_vertices` points are dropped uniformly in the unit square (on a
2030 /// torus when `torus` is `true`), and pairs *strictly* closer than
2031 /// `radius` are connected (so a zero, negative or NaN radius gives no
2032 /// edges). Vertices are numbered by increasing x coordinate. The
2033 /// coordinates are returned in the [`GeometricGraph`].
2034 ///
2035 /// Time complexity: less than O(|V|² + |E|).
2036 ///
2037 /// See also [`Graph::nearest_neighbor_graph`], which builds the same kind
2038 /// of graph (with a `cutoff` distance) from given points, and
2039 /// [`Graph::spatial_edge_lengths`] to obtain Euclidean edge weights.
2040 ///
2041 /// Binds [`igraph_grg_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_grg_game).
2042 ///
2043 /// # Examples
2044 /// ```
2045 /// use igraph::prelude::*;
2046 ///
2047 /// rng::seed(42).unwrap();
2048 /// let r = Graph::grg_game(100, 0.2, false).unwrap();
2049 /// for (u, v) in r.graph.edge_list() {
2050 /// let (u, v) = (u as usize, v as usize);
2051 /// assert!((r.x[u] - r.x[v]).hypot(r.y[u] - r.y[v]) < 0.2);
2052 /// }
2053 /// ```
2054 pub fn grg_game(num_vertices: usize, radius: f64, torus: bool) -> Result<GeometricGraph> {
2055 let n = int(num_vertices, "number of vertices")?;
2056 let mut x = Vector::new();
2057 let mut y = Vector::new();
2058 let graph =
2059 Graph::init_with(|g| unsafe { igraph_grg_game(g, n, radius, torus, &mut x, &mut y) })?;
2060 Ok(GeometricGraph {
2061 graph,
2062 x: x.into(),
2063 y: y.into(),
2064 })
2065 }
2066
2067 /// Simulates a citation network where the attractiveness of a vertex
2068 /// depends on the time since it was *last cited*.
2069 ///
2070 /// In each step a vertex is added and cites `edges_per_node` vertices.
2071 /// Time is binned into `agebins` bins of width `n / agebins + 1`;
2072 /// `preference[b]` is the attractiveness of vertices last cited `b` bins
2073 /// ago, and the last element (`preference[agebins]`) is that of vertices
2074 /// never cited, which must be positive. So `preference` has length
2075 /// `agebins + 1`. Multi-edges may appear when `edges_per_node > 1`.
2076 ///
2077 /// Time complexity: O(|V| a + |E| log |V|), `a = agebins`.
2078 ///
2079 /// Binds [`igraph_lastcit_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_lastcit_game).
2080 ///
2081 /// # Errors
2082 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
2083 /// `agebins` is zero, `preference` does not have `agebins + 1` entries,
2084 /// has negative or non-finite entries, or its last entry is not positive,
2085 /// or `num_vertices` exceeds igraph's maximum `i64::MAX - 1`.
2086 ///
2087 /// # Examples
2088 /// ```
2089 /// use igraph::prelude::*;
2090 ///
2091 /// // Only never-cited vertices are attractive: a path.
2092 /// let g = Graph::lastcit_game(9, 1, 1, &[0.0, 1.0], false).unwrap();
2093 /// let path: Vec<_> = (0..8).map(|i| (i, i + 1)).collect();
2094 /// let mut edges: Vec<_> = g.edge_list().into_iter().map(|(a, b)| (a.min(b), a.max(b))).collect();
2095 /// edges.sort();
2096 /// assert_eq!(edges, path);
2097 /// ```
2098 pub fn lastcit_game(
2099 num_vertices: usize,
2100 edges_per_node: usize,
2101 agebins: usize,
2102 preference: &[f64],
2103 directed: bool,
2104 ) -> Result<Graph> {
2105 let n = vertex_count(num_vertices)?;
2106 let e = int(edges_per_node, "edges_per_node")?;
2107 let a = int(agebins, "agebins")?;
2108 let pref = Vector::view(preference);
2109 Graph::init_with(|g| unsafe { igraph_lastcit_game(g, n, e, a, pref.as_ptr(), directed) })
2110 }
2111
2112 /// Simulates a citation network where the attractiveness of a vertex
2113 /// depends on its type (category).
2114 ///
2115 /// The graph has `types.len()` vertices; vertex `v` has type `types[v]`
2116 /// (numbered from zero). In each step one vertex is added and cites
2117 /// `edges_per_step` older vertices, chosen with probability proportional
2118 /// to `pref[type]` (`pref` must cover all types). Multi-edges may appear.
2119 /// As long as no earlier vertex has a positive attractiveness, igraph lets
2120 /// the new vertex cite *itself*, i.e. it creates self-loops (unlike the
2121 /// other citation games, which then pick a uniformly random older
2122 /// vertex).
2123 ///
2124 /// Time complexity: O((|V| + |E|) log |V|).
2125 ///
2126 /// Binds [`igraph_cited_type_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_cited_type_game).
2127 ///
2128 /// # Errors
2129 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
2130 /// negative types, negative or non-finite preferences, or types not
2131 /// covered by `pref` (non-finite preferences, which igraph 1.0.1 accepts
2132 /// and then loops forever or creates only self-loops, and types
2133 /// `>= pref.len()` are rejected on the Rust side);
2134 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) if
2135 /// `2 × types.len() × edges_per_step` overflows an `i64`.
2136 ///
2137 /// # Examples
2138 /// ```
2139 /// use igraph::prelude::*;
2140 ///
2141 /// // Only type 0 (vertex 0) is attractive: a star of double edges.
2142 /// let g = Graph::cited_type_game(&[0, 1, 1, 1, 1], &[1.0, 0.0], 2, true).unwrap();
2143 /// assert_eq!(g.ecount(), 8);
2144 /// assert!(g.edge_list().iter().all(|&(_, to)| to == 0));
2145 ///
2146 /// // Nobody is attractive: every vertex but the first cites itself.
2147 /// let g = Graph::cited_type_game(&[0, 0, 0], &[0.0], 1, true).unwrap();
2148 /// assert_eq!(g.edge_list(), [(1, 1), (2, 2)]);
2149 /// ```
2150 pub fn cited_type_game(
2151 types: &[i64],
2152 pref: &[f64],
2153 edges_per_step: usize,
2154 directed: bool,
2155 ) -> Result<Graph> {
2156 let n = int(types.len(), "number of vertices")?;
2157 let k = int(edges_per_step, "edges_per_step")?;
2158 finite_non_negative(pref, "the preferences")?;
2159 // igraph reserves `n * k` edges unchecked (a wrapped reservation lets
2160 // it push ~2^62 edges), and its error message for an uncovered type
2161 // computes `max(types) + 1`, which overflows for `i64::MAX`.
2162 n.checked_mul(k)
2163 .and_then(|x| x.checked_mul(2))
2164 .ok_or_else(|| overflow("the number of edges (vertices × edges_per_step × 2)"))?;
2165 if let Some(t) = types
2166 .iter()
2167 .find(|&&t| t < 0 || usize::try_from(t).is_ok_and(|t| t >= pref.len()))
2168 {
2169 return Err(Error::invalid(format!(
2170 "vertex types must be in 0..{} (the length of `pref`), got {t}",
2171 pref.len()
2172 )));
2173 }
2174 let t = VectorInt::view(types);
2175 let p = Vector::view(pref);
2176 Graph::init_with(|g| unsafe {
2177 igraph_cited_type_game(g, n, t.as_ptr(), p.as_ptr(), k, directed)
2178 })
2179 }
2180
2181 /// Simulates a citation network where the probability of a citation
2182 /// depends on the types of both the citing and the cited vertex.
2183 ///
2184 /// Like [`Graph::cited_type_game`], but `pref` is a square matrix:
2185 /// `pref[(i, j)]` is the attractiveness of a type `j` vertex for a
2186 /// citing vertex of type `i`.
2187 ///
2188 /// Time complexity: O((|V| + |E|) log |V|).
2189 ///
2190 /// Binds [`igraph_citing_cited_type_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_citing_cited_type_game).
2191 ///
2192 /// The matrix must be exactly `t × t`, where `t = max(types) + 1`. While
2193 /// no earlier vertex is attractive for the citing type, a uniformly
2194 /// random older vertex is cited.
2195 ///
2196 /// # Errors
2197 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
2198 /// negative types, a `pref` that is not `t × t`, or negative or
2199 /// non-finite preferences. Negative types and types `>= pref.ncol()` are
2200 /// rejected on the Rust side: igraph 1.0.1 does not check the former
2201 /// and reads out of bounds, and computes `max(types) + 1` unchecked.
2202 ///
2203 /// # Examples
2204 /// ```
2205 /// use igraph::prelude::*;
2206 ///
2207 /// // From igraph's unit tests: type i only cites type i - 1 (type 0
2208 /// // cites type 2, but nobody of type 2 exists yet): a path.
2209 /// let line = Matrix::from_rows(&[
2210 /// [0.0, 0.0, 1.0, 0.0, 0.0],
2211 /// [1.0, 0.0, 0.0, 0.0, 0.0],
2212 /// [0.0, 1.0, 0.0, 0.0, 0.0],
2213 /// [0.0, 0.0, 1.0, 0.0, 0.0],
2214 /// [0.0, 0.0, 0.0, 1.0, 0.0],
2215 /// ])
2216 /// .unwrap();
2217 /// let g = Graph::citing_cited_type_game(&[0, 1, 2, 3, 4], &line, 1, true).unwrap();
2218 /// assert_eq!(g.edge_list(), [(1, 0), (2, 1), (3, 2), (4, 3)]);
2219 /// ```
2220 pub fn citing_cited_type_game(
2221 types: &[i64],
2222 pref: &Matrix,
2223 edges_per_step: usize,
2224 directed: bool,
2225 ) -> Result<Graph> {
2226 let n = int(types.len(), "number of vertices")?;
2227 let k = int(edges_per_step, "edges_per_step")?;
2228 // Soundness: igraph indexes its per-type arrays with these values
2229 // without checking the lower bound.
2230 if let Some(t) = types.iter().find(|&&t| t < 0) {
2231 return Err(Error::invalid(format!(
2232 "vertex types must be non-negative, got {t}"
2233 )));
2234 }
2235 // igraph computes `max(types) + 1` unchecked (overflow for
2236 // `i64::MAX`); a type outside the matrix is an error anyway.
2237 let ncol = pref.ncol();
2238 if let Some(t) = types
2239 .iter()
2240 .find(|&&t| usize::try_from(t).is_ok_and(|t| t >= ncol))
2241 {
2242 return Err(Error::invalid(format!(
2243 "vertex type {t} is not covered by the preference matrix with {ncol} columns"
2244 )));
2245 }
2246 let t = VectorInt::view(types);
2247 Graph::init_with(|g| unsafe {
2248 igraph_citing_cited_type_game(g, n, t.as_ptr(), pref, k, directed)
2249 })
2250 }
2251
2252 /// Generates a simple graph made of interconnected *islands*, each a
2253 /// `G(n, p)` random graph.
2254 ///
2255 /// There are `islands_n` islands of `islands_size` vertices each (vertex
2256 /// ids are consecutive within an island); every possible edge inside an
2257 /// island is present with probability `islands_pin`, and exactly
2258 /// `n_inter` edges (at most `islands_size²`) join each pair of islands.
2259 ///
2260 /// Time complexity: O(|V| + |E|).
2261 ///
2262 /// See also [`Graph::sbm_game`], a more general planted partition model.
2263 ///
2264 /// Binds [`igraph_simple_interconnected_islands_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_simple_interconnected_islands_game).
2265 ///
2266 /// # Errors
2267 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
2268 /// `islands_pin` is not in `[0, 1]` or `n_inter > islands_size²` (the C
2269 /// documentation says larger values are clamped, but igraph 1.0.1
2270 /// reports an error). A NaN `islands_pin` is rejected on the Rust side:
2271 /// igraph 1.0.1 accepts it and then aborts the process.
2272 /// [`ErrorKind::Overflow`](crate::ErrorKind::Overflow) if
2273 /// `islands_size²`, `islands_n × islands_size` or
2274 /// `n_inter × islands_n × (islands_n - 1)` overflows an `i64` (igraph
2275 /// computes them unchecked).
2276 ///
2277 /// # Examples
2278 /// ```
2279 /// use igraph::prelude::*;
2280 ///
2281 /// rng::seed(42).unwrap();
2282 /// // 3 complete islands of 4 vertices, 2 bridges between each pair.
2283 /// let g = Graph::simple_interconnected_islands_game(3, 4, 1.0, 2).unwrap();
2284 /// assert_eq!(g.ecount(), 3 * 6 + 3 * 2);
2285 /// ```
2286 pub fn simple_interconnected_islands_game(
2287 islands_n: usize,
2288 islands_size: usize,
2289 islands_pin: f64,
2290 n_inter: usize,
2291 ) -> Result<Graph> {
2292 let a = int(islands_n, "islands_n")?;
2293 let b = int(islands_size, "islands_size")?;
2294 let c = int(n_inter, "n_inter")?;
2295 not_nan(islands_pin, "the edge probability within islands")?;
2296 // igraph computes these products in unchecked integer arithmetic.
2297 b.checked_mul(b).ok_or_else(|| overflow("islands_size²"))?;
2298 a.checked_mul(b)
2299 .ok_or_else(|| overflow("the number of vertices (islands_n × islands_size)"))?;
2300 (a.checked_sub(1))
2301 .and_then(|a1| a.checked_mul(a1))
2302 .and_then(|pairs| c.checked_mul(pairs))
2303 .ok_or_else(|| overflow("the number of inter-island edges"))?;
2304 Graph::init_with(|g| unsafe {
2305 igraph_simple_interconnected_islands_game(g, a, b, islands_pin, c)
2306 })
2307 }
2308
2309 /// Generates a random graph correlated with this (simple) graph.
2310 ///
2311 /// The adjacency matrix of `self` is perturbed so that the Pearson
2312 /// correlation between the old and new adjacency matrices is `corr`
2313 /// (in `[0, 1]`), for a graph of density `p` (in the open interval
2314 /// `(0, 1)`, typically the density of `self`). The vertices of the result
2315 /// are then permuted by `permutation` (`permutation[i]` is the vertex of
2316 /// the original graph that becomes vertex `i`), if given. The result is
2317 /// directed when `self` is. For `corr = 0` the result is simply a fresh
2318 /// `G(n, p)` graph and the permutation is ignored (only its length is
2319 /// checked).
2320 ///
2321 /// See also [`Graph::permute_vertices`] (same permutation convention)
2322 /// and [`Graph::isomorphic`]; correlated pairs are the standard benchmark
2323 /// of graph matching.
2324 ///
2325 /// Binds [`igraph_correlated_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_correlated_game).
2326 ///
2327 /// # Errors
2328 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `corr`
2329 /// is not in `[0, 1]`, `p` is not in `(0, 1)` (NaN included), `self` is
2330 /// not simple, or `permutation` does not have one entry per vertex or
2331 /// (when `corr > 0`) is not a permutation of `0..n`.
2332 ///
2333 /// # Examples
2334 /// ```
2335 /// use igraph::prelude::*;
2336 ///
2337 /// rng::seed(42).unwrap();
2338 /// let g = Graph::erdos_renyi_game_gnp(30, 0.3, false, EdgeTypeSw::Simple, false).unwrap();
2339 /// // Perfect correlation reproduces the graph...
2340 /// let h = g.correlated_game(1.0, 0.3, None).unwrap();
2341 /// assert!(g.is_same_graph(&h).unwrap());
2342 /// // ... up to the requested relabelling.
2343 /// let perm: Vec<i64> = (0..30).map(|i| (i + 1) % 30).collect();
2344 /// let h = g.correlated_game(1.0, 0.3, Some(&perm)).unwrap();
2345 /// assert!(h.is_same_graph(&g.permute_vertices(&perm).unwrap()).unwrap());
2346 /// ```
2347 pub fn correlated_game(&self, corr: f64, p: f64, permutation: Option<&[i64]>) -> Result<Graph> {
2348 not_nan(corr, "the correlation")?;
2349 not_nan(p, "the edge probability")?;
2350 let perm = permutation.map(VectorInt::view);
2351 Graph::init_with(|g| unsafe { igraph_correlated_game(g, self, corr, p, opt_ptr(&perm)) })
2352 }
2353
2354 /// Generates a pair of correlated random graphs.
2355 ///
2356 /// The first graph is a `G(n, p)` graph (simple), the second one is
2357 /// obtained from it with [`Graph::correlated_game`] with correlation
2358 /// `corr` and optional vertex `permutation`. This is exactly what
2359 /// `igraph_correlated_pair_game` does (drawing the same random numbers);
2360 /// it is implemented here by chaining the two steps because the C
2361 /// function leaks the first graph when the second step fails (still the
2362 /// case in igraph 1.0.0 and 1.0.1), which the Rust version avoids.
2363 ///
2364 /// Binds [`igraph_correlated_pair_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_correlated_pair_game).
2365 ///
2366 /// # Errors
2367 /// As for [`Graph::correlated_game`]; in particular `p` must lie in the
2368 /// open interval `(0, 1)` (the error surfaces after the first graph has
2369 /// been drawn, as in C).
2370 ///
2371 /// # Examples
2372 /// ```
2373 /// use igraph::prelude::*;
2374 ///
2375 /// rng::seed(42).unwrap();
2376 /// let (a, b) = Graph::correlated_pair_game(20, 0.0, 0.5, false, None).unwrap();
2377 /// assert_eq!((a.vcount(), b.vcount()), (20, 20));
2378 /// // From igraph's unit tests: correlation 1 gives two identical graphs.
2379 /// let (a, b) = Graph::correlated_pair_game(10, 1.0, 0.5, true, None).unwrap();
2380 /// assert!(a.is_same_graph(&b).unwrap());
2381 /// ```
2382 pub fn correlated_pair_game(
2383 num_vertices: usize,
2384 corr: f64,
2385 p: f64,
2386 directed: bool,
2387 permutation: Option<&[i64]>,
2388 ) -> Result<(Graph, Graph)> {
2389 let first =
2390 Graph::erdos_renyi_game_gnp(num_vertices, p, directed, EdgeTypeSw::Simple, false)?;
2391 let second = first.correlated_game(corr, p, permutation)?;
2392 Ok((first, second))
2393 }
2394
2395 /// Generates a uniformly random labelled tree on `num_vertices` vertices.
2396 ///
2397 /// [`RandomTreeMethod::Prufer`] samples uniform Prüfer sequences
2398 /// (undirected trees only); [`RandomTreeMethod::Lerw`] runs a
2399 /// loop-erased random walk on the complete graph (Wilson's algorithm).
2400 /// Directed trees are oriented away from the root. For `num_vertices = 0`
2401 /// the null graph is returned.
2402 ///
2403 /// See also [`Graph::is_tree`] and, for deterministic trees,
2404 /// [`Graph::kary_tree`].
2405 ///
2406 /// Binds [`igraph_tree_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_tree_game).
2407 ///
2408 /// # Errors
2409 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
2410 /// directed Prüfer trees with at least two vertices (the Prüfer method
2411 /// only supports undirected trees; for 0 or 1 vertices no method is run
2412 /// and the empty or single-vertex graph is returned).
2413 ///
2414 /// # Examples
2415 /// ```
2416 /// use igraph::prelude::*;
2417 ///
2418 /// rng::seed(42).unwrap();
2419 /// let t = Graph::tree_game(50, false, RandomTreeMethod::Lerw).unwrap();
2420 /// assert!(t.is_tree(NeighborMode::All).unwrap());
2421 /// let d = Graph::tree_game(50, true, RandomTreeMethod::Lerw).unwrap();
2422 /// assert!(d.is_tree(NeighborMode::Out).unwrap()); // an out-tree
2423 /// ```
2424 pub fn tree_game(
2425 num_vertices: usize,
2426 directed: bool,
2427 method: RandomTreeMethod,
2428 ) -> Result<Graph> {
2429 let n = int(num_vertices, "number of vertices")?;
2430 Graph::init_with(|g| unsafe { igraph_tree_game(g, n, directed, method.into()) })
2431 }
2432
2433 /// Generates a random dot product graph.
2434 ///
2435 /// Each vertex has a latent position vector, a *column* of `vecs`; two
2436 /// vertices are connected with probability equal to the dot product of
2437 /// their vectors (negative products never produce an edge, products
2438 /// above one always do, with a warning).
2439 ///
2440 /// Time complexity: O(n² m), `n` vertices, `m` the vector length.
2441 ///
2442 /// See also [`sample_sphere_surface`](crate::misc::sample_sphere_surface)
2443 /// and [`sample_dirichlet`](crate::misc::sample_dirichlet), convenient
2444 /// ways of drawing latent positions, and the adjacency spectral embedding
2445 /// ([`Graph::adjacency_spectral_embedding`]), which estimates them back.
2446 ///
2447 /// Binds [`igraph_dot_product_game`](https://igraph.org/c/html/latest/igraph-Games.html#igraph_dot_product_game).
2448 ///
2449 /// # Examples
2450 /// ```
2451 /// use igraph::prelude::*;
2452 ///
2453 /// // Orthonormal positions for vertices 0,1 vs 2: 0-1 always, 2 alone.
2454 /// let vecs = Matrix::from_rows(&[[1.0, 1.0, 0.0], [0.0, 0.0, 1.0]]).unwrap();
2455 /// let g = Graph::dot_product_game(&vecs, false).unwrap();
2456 /// assert_eq!(g.edge_list(), vec![(0, 1)]);
2457 /// ```
2458 pub fn dot_product_game(vecs: &Matrix, directed: bool) -> Result<Graph> {
2459 Graph::init_with(|g| unsafe { igraph_dot_product_game(g, vecs, directed) })
2460 }
2461}