Skip to main content

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(&degrees, 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}