Skip to main content

igraph/
community.rs

1//! Community detection, modularity, partition comparison and hierarchical
2//! random graphs (`igraph_community.h`, `igraph_hrg.h`).
3//!
4//! *Community detection* clusters the vertices of a network into groups that
5//! are densely connected internally and sparsely connected with each other.
6//! A clustering is represented, as everywhere in igraph, by a **membership
7//! vector**: `membership[v]` is the community id of vertex `v`, ids being
8//! numbered from zero. Hierarchical methods also return a **dendrogram** as a
9//! list of *merges* `(a, b)`: the `i`-th merge joins the clusters `a` and `b`
10//! into a new cluster with id `n + i` (`n` being the number of vertices).
11//!
12//! Good introductions to the topic are S. Fortunato, *Community Detection in
13//! Graphs*, Physics Reports 486 (2010) and S. Fortunato and D. Hric,
14//! *Community Detection in Networks: A User Guide*, Physics Reports 659 (2016).
15//!
16//! # Examples
17//!
18//! Two 5-cliques joined by a single edge are split into their cliques by
19//! every reasonable method:
20//!
21//! ```
22//! use igraph::prelude::*;
23//! use igraph::community::compare_communities;
24//!
25//! let mut edges = vec![];
26//! for base in [0, 5] {
27//!     for i in 0..5 {
28//!         for j in i + 1..5 {
29//!             edges.push((base + i, base + j));
30//!         }
31//!     }
32//! }
33//! edges.push((0, 5));
34//! let g = Graph::from_edges(&edges, 10, false).unwrap();
35//!
36//! let louvain = g.community_multilevel(None, 1.0).unwrap();
37//! assert_eq!(louvain.membership, vec![0, 0, 0, 0, 0, 1, 1, 1, 1, 1]);
38//! assert!((louvain.modularity() - 0.4524).abs() < 1e-4);
39//!
40//! let greedy = g.community_fastgreedy(None).unwrap();
41//! let nmi = compare_communities(&louvain.membership, &greedy.membership,
42//!                               CommunityComparison::Nmi).unwrap();
43//! assert!((nmi - 1.0).abs() < 1e-12);
44//! ```
45//!
46//! A typical workflow on a real network: Zachary's karate club (from
47//! [`Graph::famous`]) is clustered with a seeded Leiden run, and the
48//! communities are then collapsed into a weighted "community graph" with
49//! [`Graph::contract_vertices`], whose modularity for the singleton partition
50//! is, by definition, the modularity of the clustering:
51//!
52//! ```
53//! use igraph::prelude::*;
54//! use igraph::community::{LeidenObjective, LeidenOptions};
55//!
56//! let karate = Graph::famous("Zachary").unwrap();
57//! rng::seed(42).unwrap();
58//! let leiden = karate
59//!     .community_leiden_simple(None, LeidenObjective::Modularity,
60//!                              &LeidenOptions::default().with_iterations(None))
61//!     .unwrap();
62//! assert_eq!(leiden.nb_clusters, 4);
63//! assert!((leiden.quality - 0.4198).abs() < 1e-4); // the known optimum
64//!
65//! let mut quotient = karate.clone();
66//! quotient.contract_vertices(&leiden.membership).unwrap();
67//! let singletons: Vec<i64> = (0..leiden.nb_clusters as i64).collect();
68//! let q = quotient.modularity(&singletons, None, 1.0, false).unwrap();
69//! assert!((q - leiden.quality).abs() < 1e-12);
70//! ```
71//!
72//! # Provided functionality
73//!
74//! | Rust API | C function | What |
75//! |---|---|---|
76//! | [`Graph::community_multilevel`] | `igraph_community_multilevel` | Louvain modularity optimization |
77//! | [`Graph::community_leiden`] | `igraph_community_leiden` | Leiden, raw vertex weights |
78//! | [`Graph::community_leiden_simple`] | `igraph_community_leiden_simple` | Leiden with modularity / CPM / ER objective |
79//! | [`Graph::community_fastgreedy`] | `igraph_community_fastgreedy` | Clauset–Newman–Moore greedy agglomeration |
80//! | [`Graph::community_walktrap`] | `igraph_community_walktrap` | random-walk distances (Pons–Latapy) |
81//! | [`Graph::community_edge_betweenness`] | `igraph_community_edge_betweenness` | Girvan–Newman divisive method |
82//! | [`Graph::community_eb_get_merges`] | `igraph_community_eb_get_merges` | dendrogram from an edge removal order |
83//! | [`Graph::community_leading_eigenvector`], [`Graph::community_leading_eigenvector_with`] | `igraph_community_leading_eigenvector` | Newman's spectral method |
84//! | [`Graph::community_spinglass`] | `igraph_community_spinglass` | Reichardt–Bornholdt Potts model |
85//! | [`Graph::community_spinglass_single`] | `igraph_community_spinglass_single` | community of a single vertex |
86//! | [`Graph::community_label_propagation`] | `igraph_community_label_propagation` | label propagation |
87//! | [`Graph::community_infomap`] | `igraph_community_infomap` | map equation (Infomap) |
88//! | [`Graph::community_fluid_communities`] | `igraph_community_fluid_communities` | fluid communities |
89//! | [`Graph::community_voronoi`] | `igraph_community_voronoi` | Voronoi partitioning |
90//! | [`Graph::community_optimal_modularity`] | `igraph_community_optimal_modularity` | exact maximum modularity (GLPK) |
91//! | [`Graph::modularity`] | `igraph_modularity` | modularity of a partition |
92//! | [`Graph::modularity_matrix`] | `igraph_modularity_matrix` | the modularity matrix `B` |
93//! | [`Graph::coreness`] | `igraph_coreness` | k-core decomposition |
94//! | [`Graph::trussness`] | `igraph_trussness` | k-truss decomposition |
95//! | [`community_to_membership`], [`Dendrogram::cut`] | `igraph_community_to_membership` | cut a dendrogram |
96//! | [`le_community_to_membership`] | `igraph_le_community_to_membership` | cut a leading eigenvector dendrogram |
97//! | [`reindex_membership`] | `igraph_reindex_membership` | make community ids contiguous |
98//! | [`compare_communities`] | `igraph_compare_communities` | VI, NMI, split-join, (adjusted) Rand |
99//! | [`split_join_distance`] | `igraph_split_join_distance` | both projection distances |
100//! | [`Graph::hrg_fit`], [`Graph::hrg_refit`] | `igraph_hrg_fit` | fit a hierarchical random graph by MCMC |
101//! | [`Graph::hrg_consensus`], [`Graph::hrg_predict`] | `igraph_hrg_consensus`, `igraph_hrg_predict` | consensus dendrogram, missing link prediction |
102//! | [`Hrg::create`], [`Hrg::size`] | `igraph_hrg_create`, `igraph_hrg_size` | build / inspect an [`Hrg`] |
103//! | [`Hrg::sample`], [`Hrg::sample_many`], [`Graph::hrg_game`] | `igraph_hrg_sample`, `igraph_hrg_sample_many`, `igraph_hrg_game` | sample graphs from an HRG |
104//! | [`Graph::from_hrg_dendrogram`], [`Hrg::dendrogram`] | `igraph_from_hrg_dendrogram` | an HRG dendrogram as a tree |
105//!
106//! Not bound: `igraph_hrg_resize` (plain storage sizing: [`Hrg::create`]
107//! and [`Graph::hrg_fit`] size the model themselves, and resizing leaves the
108//! new tree entries uninitialized, which [`Hrg::sample`] would then read as
109//! vertex ids), and `igraph_hrg_init` / `igraph_hrg_destroy`, which the
110//! constructors and `Drop` of [`Hrg`] call.
111//!
112//! Randomized methods (Louvain, Leiden, label propagation, spinglass,
113//! Infomap, fluid communities, Voronoi generator ties, HRG, ...) draw from
114//! the random number generator of the calling thread (each thread has its
115//! own): call [`rng::seed`](crate::rng::seed) first for reproducible
116//! results, or run them with a dedicated generator through
117//! [`Rng::scoped`](crate::rng::Rng::scoped).
118//!
119//! # See also
120//!
121//! - [`components`](crate::components): [`Graph::connected_components`]
122//!   (spinglass and fluid communities need connected graphs, the leading
123//!   eigenvector method starts from the components).
124//! - [`centrality`](crate::centrality): [`Graph::edge_betweenness`], the
125//!   quantity driving [`Graph::community_edge_betweenness`].
126//! - [`mixing`](crate::mixing): [`Graph::assortativity_nominal`], whose
127//!   unnormalized value is the modularity of a partition, and
128//!   [`Graph::ecc`], the edge clustering coefficient used by
129//!   [`Graph::community_voronoi`].
130//! - [`paths`](crate::paths): [`Graph::voronoi`], the plain Voronoi
131//!   partition around given generators.
132//! - [`operators`](crate::operators): [`Graph::contract_vertices`] and
133//!   [`Graph::induced_subgraph`] to build the community graph or extract a
134//!   community.
135//! - [`games`](crate::games): [`Graph::sbm_game`] generates graphs with a
136//!   planted community structure, to benchmark the methods.
137//! - [`cliques`](crate::cliques) and [`Graph::list_triangles`] for the dense
138//!   substructures behind [`Graph::coreness`] and [`Graph::trussness`].
139
140use crate::{
141    constants::{
142        CommunityComparison, LpaVariant, NeighborMode, SpincommUpdate, SpinglassImplementation,
143    },
144    error::{Error, ErrorKind, Result, catch_panic},
145    ffi::*,
146    graph::{EdgeId, Graph, VertexId},
147    igraph_call,
148    list::{GraphList, VectorList},
149    matrix::{Matrix, MatrixInt},
150    vector::{Vector, VectorBool, VectorInt},
151};
152use std::{
153    ffi::{c_int, c_void},
154    fmt,
155    mem::MaybeUninit,
156    ops::ControlFlow,
157    ptr,
158};
159
160// ---------------------------------------------------------------------------
161// Private helpers
162// ---------------------------------------------------------------------------
163
164/// Builds a two-column merges matrix from `(a, b)` pairs.
165fn merges_to_matrix(merges: &[(i64, i64)]) -> MatrixInt {
166    let mut m = MatrixInt::zeros(merges.len(), 2);
167    for (i, &(a, b)) in merges.iter().enumerate() {
168        m[(i, 0)] = a;
169        m[(i, 1)] = b;
170    }
171    m
172}
173
174/// Reads a two-column merges matrix into `(a, b)` pairs.
175fn matrix_to_merges(m: &MatrixInt) -> Vec<(i64, i64)> {
176    if m.ncol() < 2 {
177        return Vec::new();
178    }
179    (0..m.nrow()).map(|i| (m[(i, 0)], m[(i, 1)])).collect()
180}
181
182/// Null or a pointer to the viewed weights.
183macro_rules! opt_view {
184    ($name:ident, $slice:expr) => {
185        let $name = $slice.map(Vector::view);
186        let $name = $name.as_ref().map_or(ptr::null(), |v| v.as_ptr());
187    };
188}
189
190/// Validates the first `steps` rows of a merges matrix over `nodes` leaves.
191///
192/// igraph's `igraph_community_to_membership` (igraph 1.0.0 and 1.0.1) indexes
193/// its work arrays with the cluster ids found in `merges` without any bounds
194/// check, so malformed input would make it read and write out of bounds. Here we require that the `i`-th
195/// merge only refers to leaves (`0..nodes`) or to clusters created by earlier
196/// merges (`nodes..nodes + i`), and that no cluster is merged twice.
197///
198/// The bookkeeping is proportional to the number of merges, not to `nodes`:
199/// a huge `nodes` is left to igraph, which reports a clean out-of-memory
200/// error when it cannot allocate its `nodes`-long work vectors.
201fn check_merges(merges: &[(i64, i64)], nodes: usize, steps: usize) -> Result<()> {
202    if nodes > igraph_int_t::MAX as usize {
203        return Err(Error::invalid(format!(
204            "the number of leaves ({nodes}) exceeds the largest igraph integer"
205        )));
206    }
207    let steps = steps.min(merges.len());
208    let mut used = std::collections::HashSet::with_capacity(2 * steps);
209    for (i, &(a, b)) in merges[..steps].iter().enumerate() {
210        // `nodes <= i64::MAX` and `i < merges.len() <= isize::MAX`: no overflow.
211        let limit = nodes + i;
212        for c in [a, b] {
213            if !usize::try_from(c).is_ok_and(|c| c < limit) {
214                return Err(Error::invalid(format!(
215                    "merge {i} refers to cluster {c}, but only clusters 0..{limit} exist at that point"
216                )));
217            }
218            if !used.insert(c) {
219                return Err(Error::invalid(format!(
220                    "the merges contain multiple merges of cluster {c}"
221                )));
222            }
223        }
224    }
225    Ok(())
226}
227
228/// Rejects `NaN` and infinite entries (which several igraph routines do not
229/// check, see the callers).
230fn check_finite(what: &str, values: Option<&[f64]>) -> Result<()> {
231    if let Some(&x) = values.and_then(|v| v.iter().find(|x| !x.is_finite())) {
232        return Err(Error::invalid(format!(
233            "{what} must only contain finite values, found {x}"
234        )));
235    }
236    Ok(())
237}
238
239/// Sum of the absolute values (`inf` on overflow).
240fn abs_sum(values: &[f64]) -> f64 {
241    values.iter().map(|x| x.abs()).sum()
242}
243
244/// Largest sum of absolute weights accepted by the ARPACK-based leading
245/// eigenvector method and by spinglass: squares of such sums stay finite.
246const MAX_ABS_WEIGHT_SUM: f64 = 1e150;
247
248/// Largest spinglass starting temperature accepted: igraph multiplies it by
249/// 1.1 twice while estimating the actual start temperature.
250const MAX_SPINGLASS_TEMPERATURE: f64 = 1e300;
251
252/// Largest magnitude of the spinglass resolution parameters accepted: they
253/// multiply (sums of) weights, bounded by [`MAX_ABS_WEIGHT_SUM`], so the
254/// energies stay finite. With `gamma = gamma_minus = f64::MAX` the `Neg`
255/// implementation never terminates.
256const MAX_SPINGLASS_GAMMA: f64 = 1e150;
257
258/// Validates what igraph's spinglass code does not (igraph 1.0.0 and 1.0.1):
259/// non-finite values make its annealing loops run forever, and a huge number
260/// of spins overflows its `spins + 1` sized allocations.
261fn check_spinglass(weights: Option<&[f64]>, o: &SpinglassOptions, annealing: bool) -> Result<()> {
262    check_finite("the weight vector", weights)?;
263    // Overflowing weight sums turn the energies into NaN: same endless loop.
264    if let Some(w) = weights
265        && abs_sum(w) > MAX_ABS_WEIGHT_SUM
266    {
267        return Err(Error::invalid(format!(
268            "the sum of the absolute weights must be at most {MAX_ABS_WEIGHT_SUM:e}, got {:e}",
269            abs_sum(w)
270        )));
271    }
272    if !(2..=i32::MAX as usize).contains(&o.spins) {
273        return Err(Error::invalid(format!(
274            "the number of spins must be in 2..={}, got {}",
275            i32::MAX,
276            o.spins
277        )));
278    }
279    if !(0.0..=MAX_SPINGLASS_GAMMA).contains(&o.gamma) {
280        return Err(Error::invalid(format!(
281            "gamma must be in [0, {MAX_SPINGLASS_GAMMA:e}], got {}",
282            o.gamma
283        )));
284    }
285    if !annealing {
286        return Ok(());
287    }
288    // `gamma_minus` is only used (and only checked) by the `Neg` implementation.
289    if o.implementation == SpinglassImplementation::Neg
290        && !(-MAX_SPINGLASS_GAMMA..=MAX_SPINGLASS_GAMMA).contains(&o.gamma_minus)
291    {
292        return Err(Error::invalid(format!(
293            "gamma_minus must be in [-{MAX_SPINGLASS_GAMMA:e}, {MAX_SPINGLASS_GAMMA:e}], got {}",
294            o.gamma_minus
295        )));
296    }
297    if !(0.0..1.0).contains(&o.cooling_factor) {
298        return Err(Error::invalid(format!(
299            "the cooling factor must be in [0, 1), got {}",
300            o.cooling_factor
301        )));
302    }
303    for (what, t) in [
304        ("starting", o.start_temperature),
305        ("stopping", o.stop_temperature),
306    ] {
307        if !(0.0..=MAX_SPINGLASS_TEMPERATURE).contains(&t) {
308            return Err(Error::invalid(format!(
309                "the {what} temperature must be in [0, {MAX_SPINGLASS_TEMPERATURE:e}], got {t}"
310            )));
311        }
312    }
313    Ok(())
314}
315
316fn check_len(what: &str, len: usize, expected: usize) -> Result<()> {
317    if len != expected {
318        return Err(Error::invalid(format!(
319            "{what} has length {len}, but {expected} was expected"
320        )));
321    }
322    Ok(())
323}
324
325/// Number of distinct values in a membership vector (ignoring negative ids).
326fn count_distinct(membership: &[i64]) -> usize {
327    let mut ids: Vec<i64> = membership.iter().copied().filter(|&c| c >= 0).collect();
328    ids.sort_unstable();
329    ids.dedup();
330    ids.len()
331}
332
333/// Groups vertex ids by community id (`membership` must be non-negative).
334fn groups_of(membership: &[i64]) -> Vec<Vec<VertexId>> {
335    let k = membership
336        .iter()
337        .copied()
338        .max()
339        .map_or(0, |m| (m + 1).max(0) as usize);
340    let mut groups = vec![Vec::new(); k];
341    for (v, &c) in membership.iter().enumerate() {
342        if c >= 0 {
343            groups[c as usize].push(v as VertexId);
344        }
345    }
346    groups
347}
348
349/// Community sizes, indexed by community id.
350fn sizes_of(membership: &[i64]) -> Vec<usize> {
351    groups_of(membership).iter().map(Vec::len).collect()
352}
353
354macro_rules! membership_helpers {
355    ($ty:ident) => {
356        impl $ty {
357            /// Number of communities in [`membership`](Self::membership).
358            pub fn num_communities(&self) -> usize {
359                count_distinct(&self.membership)
360            }
361
362            /// Size of each community, indexed by community id.
363            pub fn sizes(&self) -> Vec<usize> {
364                sizes_of(&self.membership)
365            }
366
367            /// The vertices of each community, indexed by community id.
368            pub fn communities(&self) -> Vec<Vec<VertexId>> {
369                groups_of(&self.membership)
370            }
371        }
372    };
373}
374
375// ---------------------------------------------------------------------------
376// Enums
377// ---------------------------------------------------------------------------
378
379crate::ffi_enum! {
380    /// Objective function optimized by [`Graph::community_leiden_simple`]
381    /// (`igraph_leiden_objective_t`).
382    ///
383    /// With `A` the adjacency matrix, `m` the total edge weight, `k` the
384    /// degrees, `γ` the resolution and `δ(c_i, c_j)` the co-membership
385    /// indicator:
386    pub enum LeidenObjective: igraph_leiden_objective_t {
387        /// Generalized modularity `Q = 1/(2m) Σ_ij (A_ij − γ k_i k_j / (2m)) δ(c_i, c_j)`
388        /// (directed: `1/m Σ_ij (A_ij − γ k^out_i k^in_j / m) δ(c_i, c_j)`),
389        /// i.e. a configuration model null model. Weights must be non-negative.
390        Modularity = igraph_leiden_objective_t_IGRAPH_LEIDEN_OBJECTIVE_MODULARITY,
391        /// Constant Potts model `Q = 1/(2m) Σ_ij (A_ij − γ) δ(c_i, c_j)`, free of
392        /// the resolution limit. Negative weights are allowed.
393        Cpm = igraph_leiden_objective_t_IGRAPH_LEIDEN_OBJECTIVE_CPM,
394        /// Erdős–Rényi null model `Q = 1/(2m) Σ_ij (A_ij − γ p) δ(c_i, c_j)`,
395        /// `p` being the weighted density. Weights must be non-negative.
396        ErdosRenyi = igraph_leiden_objective_t_IGRAPH_LEIDEN_OBJECTIVE_ER,
397    }
398}
399
400// ---------------------------------------------------------------------------
401// Result types
402// ---------------------------------------------------------------------------
403
404/// A flat partition of the vertices together with its modularity.
405///
406/// Returned by [`Graph::community_optimal_modularity`].
407#[derive(Debug, Clone, PartialEq)]
408pub struct Clustering {
409    /// Community id of each vertex, numbered from zero.
410    pub membership: Vec<i64>,
411    /// Modularity of the partition (`NaN` for graphs without edges).
412    pub modularity: f64,
413}
414membership_helpers!(Clustering);
415
416/// Result of the multi-level (Louvain) algorithm,
417/// see [`Graph::community_multilevel`].
418#[derive(Debug, Clone, PartialEq)]
419pub struct Multilevel {
420    /// Community id of each vertex in the final (best) level.
421    pub membership: Vec<i64>,
422    /// Membership vector after each aggregation level, coarsest last (the
423    /// last one equals [`membership`](Self::membership)). Empty if no merge
424    /// improved modularity, in which case every vertex is its own community.
425    pub levels: Vec<Vec<i64>>,
426    /// Modularity (at the requested resolution) after each level, in the
427    /// order of [`levels`](Self::levels); if `levels` is empty, the single
428    /// value is the modularity of the singleton partition.
429    pub modularities: Vec<f64>,
430}
431membership_helpers!(Multilevel);
432
433impl Multilevel {
434    /// Modularity of the final partition (the last entry of
435    /// [`modularities`](Self::modularities)), `NaN` if it is empty.
436    pub fn modularity(&self) -> f64 {
437        self.modularities.last().copied().unwrap_or(f64::NAN)
438    }
439}
440
441/// Result of the Leiden algorithm, see [`Graph::community_leiden`].
442#[derive(Debug, Clone, PartialEq)]
443pub struct Leiden {
444    /// Community id of each vertex.
445    pub membership: Vec<i64>,
446    /// Number of clusters in [`membership`](Self::membership).
447    pub nb_clusters: usize,
448    /// Value of the optimized objective function (quality) of the partition.
449    pub quality: f64,
450}
451membership_helpers!(Leiden);
452
453/// A hierarchical clustering (dendrogram) together with the modularity of
454/// each of its levels and the best cut.
455///
456/// Returned by [`Graph::community_fastgreedy`], [`Graph::community_walktrap`]
457/// and [`Graph::community_eb_get_merges`].
458#[derive(Debug, Clone, PartialEq)]
459pub struct Dendrogram {
460    /// Number of leaves, i.e. vertices of the clustered graph.
461    pub num_vertices: usize,
462    /// The merges: the `i`-th pair `(a, b)` joins clusters `a` and `b` into
463    /// cluster `num_vertices + i`; ids below `num_vertices` are single vertices.
464    pub merges: Vec<(i64, i64)>,
465    /// Modularity before the first merge and after each merge
466    /// (`merges.len() + 1` values), empty if not computed.
467    pub modularity: Vec<f64>,
468    /// Membership vector of the cut with the highest modularity.
469    pub membership: Vec<i64>,
470}
471membership_helpers!(Dendrogram);
472
473impl Dendrogram {
474    /// The highest modularity along the dendrogram (`NaN` if unknown).
475    pub fn max_modularity(&self) -> f64 {
476        self.modularity.iter().copied().fold(f64::NAN, f64::max)
477    }
478
479    /// Cuts the dendrogram into `num_communities` clusters, returning the
480    /// membership vector (see [`community_to_membership`]).
481    ///
482    /// This is how to get a clustering with a prescribed number of
483    /// communities instead of the modularity-maximizing
484    /// [`membership`](Self::membership).
485    ///
486    /// # Errors
487    /// [`ErrorKind::InvalidValue`] if `num_communities` is zero (for a
488    /// non-empty graph), exceeds the number of vertices, or the dendrogram
489    /// has not enough merges to reach it.
490    ///
491    /// # Examples
492    /// ```
493    /// use igraph::prelude::*;
494    /// // Three triangles in a row, joined by single edges.
495    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3),
496    ///                             (6, 7), (7, 8), (8, 6), (2, 3), (5, 6)], 9, false).unwrap();
497    /// let d = g.community_walktrap(None, 4).unwrap();
498    /// assert_eq!(d.cut(3).unwrap(), d.membership); // the best cut has 3 clusters
499    /// let two = d.cut(2).unwrap();
500    /// assert_eq!(two.iter().filter(|&&c| c == two[0]).count() % 3, 0);
501    /// assert_eq!(d.cut(1).unwrap(), vec![0; 9]);
502    /// assert!(d.cut(10).is_err());
503    /// ```
504    pub fn cut(&self, num_communities: usize) -> Result<Vec<i64>> {
505        if num_communities > self.num_vertices || num_communities == 0 && self.num_vertices > 0 {
506            return Err(Error::invalid(format!(
507                "cannot cut a dendrogram over {} vertices into {num_communities} clusters",
508                self.num_vertices
509            )));
510        }
511        let steps = self.num_vertices - num_communities;
512        community_to_membership(&self.merges, self.num_vertices, steps).map(|(m, _)| m)
513    }
514}
515
516/// Result of the Girvan–Newman algorithm, see [`Graph::community_edge_betweenness`].
517#[derive(Debug, Clone, PartialEq)]
518pub struct EdgeBetweennessCommunities {
519    /// The ids of the removed edges, in order of removal.
520    pub removed_edges: Vec<EdgeId>,
521    /// Betweenness of each removed edge at the moment of its removal
522    /// (not divided by the weights).
523    pub edge_betweenness: Vec<f64>,
524    /// The dendrogram, obtained by replaying the removals backwards.
525    pub merges: Vec<(i64, i64)>,
526    /// Indices into [`removed_edges`](Self::removed_edges) of the edges whose
527    /// removal split a component, in reverse order.
528    pub bridges: Vec<i64>,
529    /// Modularity of each division: before the first merge (all components
530    /// split into single vertices) and after each merge, i.e.
531    /// `merges.len() + 1` values in the order of [`merges`](Self::merges).
532    pub modularity: Vec<f64>,
533    /// Membership vector of the division with the highest modularity.
534    pub membership: Vec<i64>,
535}
536membership_helpers!(EdgeBetweennessCommunities);
537
538/// Result of [`Graph::community_eb_get_merges`].
539#[derive(Debug, Clone, PartialEq)]
540pub struct EdgeRemovalMerges {
541    /// The dendrogram with its modularity values and best membership.
542    pub dendrogram: Dendrogram,
543    /// Indices into the given edge sequence of the edges whose removal split a
544    /// component, in reverse order.
545    pub bridges: Vec<i64>,
546}
547
548/// One step of the history of [`Graph::community_leading_eigenvector`]
549/// (`igraph_leading_eigenvector_community_history_t`).
550#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
551pub enum LeadingEigenvectorEvent {
552    /// The algorithm started from the connected components of the graph.
553    StartFull,
554    /// The algorithm started from a given partition with this many communities.
555    StartGiven {
556        /// Initial number of communities.
557        communities: i64,
558    },
559    /// A community was split in two: the first part keeps the id, the second
560    /// one gets the number of communities before the split as id.
561    Split {
562        /// The id of the split community.
563        community: i64,
564    },
565    /// Splitting the community would not increase modularity.
566    Failed {
567        /// The id of the community that was not split.
568        community: i64,
569    },
570}
571
572fn decode_history(raw: &[i64]) -> Vec<LeadingEigenvectorEvent> {
573    let mut out = Vec::new();
574    let mut i = 0;
575    while i < raw.len() {
576        let code = raw[i] as igraph_leading_eigenvector_community_history_t;
577        let next = raw.get(i + 1).copied().unwrap_or(-1);
578        match code {
579            igraph_leading_eigenvector_community_history_t_IGRAPH_LEVC_HIST_START_FULL => {
580                out.push(LeadingEigenvectorEvent::StartFull);
581                i += 1;
582            }
583            igraph_leading_eigenvector_community_history_t_IGRAPH_LEVC_HIST_START_GIVEN => {
584                out.push(LeadingEigenvectorEvent::StartGiven { communities: next });
585                i += 2;
586            }
587            igraph_leading_eigenvector_community_history_t_IGRAPH_LEVC_HIST_SPLIT => {
588                out.push(LeadingEigenvectorEvent::Split { community: next });
589                i += 2;
590            }
591            igraph_leading_eigenvector_community_history_t_IGRAPH_LEVC_HIST_FAILED => {
592                out.push(LeadingEigenvectorEvent::Failed { community: next });
593                i += 2;
594            }
595            _ => i += 1,
596        }
597    }
598    out
599}
600
601/// Result of Newman's leading eigenvector method,
602/// see [`Graph::community_leading_eigenvector`].
603#[derive(Debug, Clone, PartialEq)]
604pub struct LeadingEigenvector {
605    /// Community id of each vertex after all the splits.
606    pub membership: Vec<i64>,
607    /// The splits, replayed backwards as merges of *community* ids (not
608    /// vertex ids): with `p` final communities, the first pair forms
609    /// community `p`, the second `p + 1`, ... Use
610    /// [`le_community_to_membership`] to undo splits.
611    pub merges: Vec<(i64, i64)>,
612    /// Modularity of the final division.
613    pub modularity: f64,
614    /// Eigenvalue computed at each step (`NaN` for steps given by the
615    /// initial partition); non-positive values did not result in a split.
616    pub eigenvalues: Vec<f64>,
617    /// Eigenvector computed at each step, restricted to the vertices of the
618    /// community being split (empty for steps given by the initial partition).
619    pub eigenvectors: Vec<Vec<f64>>,
620    /// A trace of the algorithm.
621    pub history: Vec<LeadingEigenvectorEvent>,
622}
623membership_helpers!(LeadingEigenvector);
624
625/// The state passed to the callback of
626/// [`Graph::community_leading_eigenvector_with`] after each eigenvector
627/// computation.
628pub struct LeadingEigenvectorStep<'a> {
629    membership: &'a [i64],
630    community: i64,
631    eigenvalue: f64,
632    eigenvector: &'a [f64],
633    multiplier: igraph_arpack_function_t,
634    arpack_extra: *mut c_void,
635}
636
637impl fmt::Debug for LeadingEigenvectorStep<'_> {
638    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
639        f.debug_struct("LeadingEigenvectorStep")
640            .field("membership", &self.membership)
641            .field("community", &self.community)
642            .field("eigenvalue", &self.eigenvalue)
643            .field("eigenvector", &self.eigenvector)
644            .finish()
645    }
646}
647
648impl LeadingEigenvectorStep<'_> {
649    /// The current membership vector, before applying the split implied by
650    /// this eigenvector.
651    pub fn membership(&self) -> &[i64] {
652        self.membership
653    }
654
655    /// The id of the community the algorithm is trying to split.
656    pub fn community(&self) -> i64 {
657        self.community
658    }
659
660    /// The leading eigenvalue just found; the community is split only if it
661    /// is positive.
662    pub fn eigenvalue(&self) -> f64 {
663        self.eigenvalue
664    }
665
666    /// The corresponding eigenvector, with one element per vertex of the
667    /// community (in increasing vertex id order).
668    pub fn eigenvector(&self) -> &[f64] {
669        self.eigenvector
670    }
671
672    /// Multiplies `x` by the (generalized) modularity matrix of the community
673    /// being split, i.e. runs the ARPACK matrix-vector product igraph used to
674    /// find this eigenvector. `x` must have the length of
675    /// [`eigenvector`](Self::eigenvector).
676    ///
677    /// For instance `step.multiply(step.eigenvector())` is
678    /// `eigenvalue * eigenvector` up to numerical accuracy.
679    ///
680    /// # Errors
681    /// If `x` has a wrong length.
682    pub fn multiply(&self, x: &[f64]) -> Result<Vec<f64>> {
683        check_len("the vector to multiply", x.len(), self.eigenvector.len())?;
684        let mut to = vec![0.0; x.len()];
685        let Some(f) = self.multiplier else {
686            return Err(Error::invalid("no ARPACK multiplier available"));
687        };
688        // Called from inside the leading eigenvector callback: no
689        // `igraph_call!` here, so forget any stale error record ourselves.
690        crate::error::reset_last_error();
691        // igraph's multiplier writes `n` values into `to` and reads `n` from `from`.
692        let code = unsafe {
693            f(
694                to.as_mut_ptr(),
695                x.as_ptr(),
696                x.len() as c_int,
697                self.arpack_extra,
698            )
699        };
700        crate::error::check(code)?;
701        Ok(to)
702    }
703}
704
705/// Result of the spinglass method, see [`Graph::community_spinglass`].
706#[derive(Debug, Clone, PartialEq)]
707pub struct Spinglass {
708    /// Community id of each vertex.
709    pub membership: Vec<i64>,
710    /// Size of each community, indexed by community id.
711    pub csize: Vec<i64>,
712    /// Generalized modularity (with resolution `gamma`) of the result.
713    pub modularity: f64,
714    /// Temperature at the end of the simulated annealing.
715    pub temperature: f64,
716}
717membership_helpers!(Spinglass);
718
719/// Result of [`Graph::community_spinglass_single`].
720#[derive(Debug, Clone, PartialEq)]
721pub struct SpinglassSingle {
722    /// The vertices in the community of the given vertex.
723    pub community: Vec<VertexId>,
724    /// Cohesion index of the community.
725    pub cohesion: f64,
726    /// Adhesion index of the community.
727    pub adhesion: f64,
728    /// Number (or total weight) of the edges inside the community.
729    pub inner_links: f64,
730    /// Number (or total weight) of the edges leaving the community.
731    pub outer_links: f64,
732}
733
734/// Result of Infomap, see [`Graph::community_infomap`].
735#[derive(Debug, Clone, PartialEq)]
736pub struct Infomap {
737    /// Community id of each vertex.
738    pub membership: Vec<i64>,
739    /// Code length (in bits) of the partition: the expected description
740    /// length of a random walk step under the map equation.
741    pub codelength: f64,
742}
743membership_helpers!(Infomap);
744
745/// Result of Voronoi partitioning, see [`Graph::community_voronoi`].
746#[derive(Debug, Clone, PartialEq)]
747pub struct Voronoi {
748    /// Community id of each vertex.
749    pub membership: Vec<i64>,
750    /// The generator vertex of each community.
751    pub generators: Vec<VertexId>,
752    /// Modularity of the partition.
753    pub modularity: f64,
754}
755membership_helpers!(Voronoi);
756
757// ---------------------------------------------------------------------------
758// Options
759// ---------------------------------------------------------------------------
760
761/// Options of [`Graph::community_leiden`] and [`Graph::community_leiden_simple`].
762#[derive(Debug, Clone, PartialEq)]
763pub struct LeidenOptions<'a> {
764    /// Resolution parameter `γ` (default `1.0`). Note that for the raw
765    /// [`Graph::community_leiden`] interface, modularity is obtained with
766    /// degrees as vertex weights and `γ = 1 / (2m)`.
767    pub resolution: f64,
768    /// Randomness in the refinement step (default `0.01`).
769    pub beta: f64,
770    /// Number of iterations of the core algorithm (default `Some(2)`);
771    /// `None` iterates until an iteration does not change the partition.
772    pub iterations: Option<u32>,
773    /// Starting partition (default `None`: singletons).
774    pub initial: Option<&'a [i64]>,
775}
776
777impl Default for LeidenOptions<'_> {
778    fn default() -> Self {
779        Self {
780            resolution: 1.0,
781            beta: 0.01,
782            iterations: Some(2),
783            initial: None,
784        }
785    }
786}
787
788impl<'a> LeidenOptions<'a> {
789    /// Sets the resolution parameter.
790    pub fn with_resolution(mut self, resolution: f64) -> Self {
791        self.resolution = resolution;
792        self
793    }
794    /// Sets the refinement randomness `beta`.
795    pub fn with_beta(mut self, beta: f64) -> Self {
796        self.beta = beta;
797        self
798    }
799    /// Sets the number of iterations (`None`: until convergence).
800    pub fn with_iterations(mut self, iterations: Option<u32>) -> Self {
801        self.iterations = iterations;
802        self
803    }
804    /// Sets the starting partition.
805    pub fn with_initial(mut self, initial: &'a [i64]) -> Self {
806        self.initial = Some(initial);
807        self
808    }
809}
810
811/// Options of [`Graph::community_spinglass`] and
812/// [`Graph::community_spinglass_single`], with the defaults suggested by igraph.
813#[derive(Debug, Clone, PartialEq)]
814pub struct SpinglassOptions {
815    /// Number of spins, i.e. the maximum number of communities (default `25`).
816    pub spins: usize,
817    /// Update all spins in parallel (default `false`); not supported by
818    /// [`SpinglassImplementation::Neg`].
819    pub parallel_update: bool,
820    /// Starting temperature (default `1.0`).
821    pub start_temperature: f64,
822    /// Stopping temperature (default `0.01`).
823    pub stop_temperature: f64,
824    /// Cooling factor of the simulated annealing (default `0.99`).
825    pub cooling_factor: f64,
826    /// Null model: [`SpincommUpdate::Config`] (configuration model, default)
827    /// or [`SpincommUpdate::Simple`] (Erdős–Rényi).
828    pub update_rule: SpincommUpdate,
829    /// Resolution parameter `γ` (default `1.0`, must be in `[0, 1e150]`).
830    pub gamma: f64,
831    /// Implementation: [`SpinglassImplementation::Orig`] (default, faster) or
832    /// [`SpinglassImplementation::Neg`] (allows negative weights).
833    pub implementation: SpinglassImplementation,
834    /// Resolution for the negative part of the network, `Neg` only (default
835    /// `1.0`, magnitude at most `1e150`).
836    pub gamma_minus: f64,
837}
838
839impl Default for SpinglassOptions {
840    fn default() -> Self {
841        Self {
842            spins: 25,
843            parallel_update: false,
844            start_temperature: 1.0,
845            stop_temperature: 0.01,
846            cooling_factor: 0.99,
847            update_rule: SpincommUpdate::Config,
848            gamma: 1.0,
849            implementation: SpinglassImplementation::Orig,
850            gamma_minus: 1.0,
851        }
852    }
853}
854
855/// Options of [`Graph::community_label_propagation`].
856#[derive(Debug, Clone, PartialEq)]
857pub struct LabelPropagationOptions<'a> {
858    /// Direction of label propagation in directed graphs (default
859    /// [`NeighborMode::All`]: ignore directions). `Out` propagates labels
860    /// along the edges, `In` backwards.
861    pub mode: NeighborMode,
862    /// Initial labels, one per vertex: a label is an id in `0..vcount`, and
863    /// negative values mean "unlabeled" (default `None`: every vertex has its
864    /// own label). Label values carry no meaning and may be renumbered; only
865    /// co-membership matters.
866    pub initial: Option<&'a [i64]>,
867    /// Which initial labels are fixed (only meaningful with
868    /// [`initial`](Self::initial); unlabeled vertices cannot be fixed, and
869    /// igraph ignores their flag with a warning). Fixed vertices keep their
870    /// co-membership: two fixed vertices end up in the same community iff
871    /// they had the same initial label.
872    pub fixed: Option<&'a [bool]>,
873    /// Algorithm variant (default [`LpaVariant::Dominance`]).
874    pub variant: LpaVariant,
875}
876
877impl Default for LabelPropagationOptions<'_> {
878    fn default() -> Self {
879        Self {
880            mode: NeighborMode::All,
881            initial: None,
882            fixed: None,
883            variant: LpaVariant::Dominance,
884        }
885    }
886}
887
888/// Options of [`Graph::community_infomap`].
889#[derive(Debug, Clone, PartialEq)]
890pub struct InfomapOptions {
891    /// Number of attempts to partition the network, the best one is kept
892    /// (default `10`, at least `1` and at most `u32::MAX`).
893    pub trials: usize,
894    /// Add a Bayesian prior network to avoid overfitting missing links
895    /// (default `false`).
896    pub regularized: bool,
897    /// Multiplier of the default regularization strength (default `1.0`).
898    pub regularization_strength: f64,
899}
900
901impl Default for InfomapOptions {
902    fn default() -> Self {
903        Self {
904            trials: 10,
905            regularized: false,
906            regularization_strength: 1.0,
907        }
908    }
909}
910
911// ---------------------------------------------------------------------------
912// Non-graph functions
913// ---------------------------------------------------------------------------
914
915/// Cuts a dendrogram after `steps` merges, returning the membership vector
916/// and the size of each community.
917///
918/// The dendrogram has `nodes` leaves (the vertices) and is given by its
919/// `merges`, in the format of [`Graph::community_fastgreedy`],
920/// [`Graph::community_walktrap`] or [`Graph::community_edge_betweenness`]:
921/// the `i`-th pair joins two dendrogram nodes into node `nodes + i`. After
922/// `steps` merges, `nodes - steps` communities remain, numbered from zero.
923/// `steps` may not exceed the number of merges. Time complexity: O(|V|).
924/// [`Dendrogram::cut`] does the same given a number of communities.
925///
926/// Binds [`igraph_community_to_membership`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_to_membership).
927///
928/// # Errors
929/// [`ErrorKind::InvalidValue`] if `steps`
930/// is too large or the merges are malformed.
931///
932/// # Examples
933/// ```
934/// use igraph::community::community_to_membership;
935/// // 4 leaves: merge 0+1 into 4, 2+3 into 5, then 4+5 into 6.
936/// let merges = [(0, 1), (2, 3), (4, 5)];
937/// let (membership, sizes) = community_to_membership(&merges, 4, 2).unwrap();
938/// assert_eq!(membership, vec![1, 1, 0, 0]);
939/// assert_eq!(sizes, vec![2, 2]);
940/// ```
941pub fn community_to_membership(
942    merges: &[(i64, i64)],
943    nodes: usize,
944    steps: usize,
945) -> Result<(Vec<i64>, Vec<i64>)> {
946    check_merges(merges, nodes, steps)?;
947    let m = merges_to_matrix(merges);
948    let mut membership = VectorInt::new();
949    let mut csize = VectorInt::new();
950    igraph_call!(igraph_community_to_membership(
951        &m,
952        nodes as igraph_int_t,
953        steps as igraph_int_t,
954        &mut membership,
955        &mut csize
956    ))?;
957    Ok((membership.into(), csize.into()))
958}
959
960/// Applies `steps` merges of a leading eigenvector dendrogram to an initial
961/// partition, returning the new membership vector and community sizes.
962///
963/// Unlike [`community_to_membership`], the dendrogram leaves are the `m`
964/// communities of `membership` (ids `0..m`, contiguous), and the `i`-th merge
965/// forms community `m + i`, as produced by
966/// [`Graph::community_leading_eigenvector`]. The result has `m - steps`
967/// communities. Time complexity: O(|V|).
968///
969/// Binds [`igraph_le_community_to_membership`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_le_community_to_membership).
970///
971/// # Errors
972/// [`ErrorKind::InvalidValue`] for
973/// non-contiguous or negative ids, too many steps, or merges referring to
974/// already merged clusters.
975///
976/// # Examples
977/// ```
978/// use igraph::community::le_community_to_membership;
979/// let (membership, sizes) = le_community_to_membership(&[(1, 3)], 1, &[0, 1, 2, 3, 4]).unwrap();
980/// assert_eq!(membership, vec![1, 0, 2, 0, 3]);
981/// assert_eq!(sizes, vec![2, 1, 1, 1]);
982/// ```
983pub fn le_community_to_membership(
984    merges: &[(i64, i64)],
985    steps: usize,
986    membership: &[i64],
987) -> Result<(Vec<i64>, Vec<i64>)> {
988    // The leaves of the dendrogram are the initial communities.
989    let components = membership.iter().max().map_or(0, |&m| m.saturating_add(1));
990    if components > membership.len() as i64 {
991        return Err(Error::invalid(format!(
992            "invalid membership vector: {components} communities for {} elements",
993            membership.len()
994        )));
995    }
996    check_merges(merges, components.max(0) as usize, steps)?;
997    let m = merges_to_matrix(merges);
998    let mut memb = VectorInt::from_slice(membership);
999    let mut csize = VectorInt::new();
1000    igraph_call!(igraph_le_community_to_membership(
1001        &m,
1002        steps as igraph_int_t,
1003        &mut memb,
1004        &mut csize
1005    ))?;
1006    Ok((memb.into(), csize.into()))
1007}
1008
1009/// Relabels a membership vector in place so that community ids are
1010/// `0..k`, and returns the mapping from new to old ids (its length `k` is the
1011/// number of communities).
1012///
1013/// When all ids lie in `0..n` (`n` being the length of `membership`), new
1014/// ids are assigned in order of first appearance; otherwise (negative or
1015/// large ids) they follow the increasing order of the old ids.
1016/// Time complexity: O(n) in the first case, O(n log n) otherwise.
1017///
1018/// Binds [`igraph_reindex_membership`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_reindex_membership).
1019///
1020/// # Examples
1021/// ```
1022/// use igraph::community::reindex_membership;
1023/// // Ids in 0..4: numbered by first appearance.
1024/// let mut membership = vec![3, 3, 1, 1];
1025/// assert_eq!(reindex_membership(&mut membership).unwrap(), vec![3, 1]);
1026/// assert_eq!(membership, vec![0, 0, 1, 1]);
1027/// // An id outside 0..4: numbered in increasing order of the old ids.
1028/// let mut membership = vec![7, 3, 7, 10];
1029/// let new_to_old = reindex_membership(&mut membership).unwrap();
1030/// assert_eq!(membership, vec![1, 0, 1, 2]);
1031/// assert_eq!(new_to_old, vec![3, 7, 10]);
1032/// ```
1033pub fn reindex_membership(membership: &mut [i64]) -> Result<Vec<i64>> {
1034    let mut memb = VectorInt::from_slice(membership);
1035    let mut new_to_old = VectorInt::new();
1036    let mut nb: igraph_int_t = 0;
1037    igraph_call!(igraph_reindex_membership(
1038        &mut memb,
1039        &mut new_to_old,
1040        &mut nb
1041    ))?;
1042    membership.copy_from_slice(&memb);
1043    Ok(new_to_old.into())
1044}
1045
1046/// Compares two partitions of the same set with the given measure.
1047///
1048/// - [`CommunityComparison::Vi`]: variation of information (Meilă 2003),
1049///   `VI = H(C1) + H(C2) − 2 MI(C1, C2)` in natural units; 0 iff equal.
1050/// - [`CommunityComparison::Nmi`]: normalized mutual information (Danon et
1051///   al. 2005), `2 MI / (H(C1) + H(C2))` in `(0, 1]`; 1 iff equal.
1052/// - [`CommunityComparison::SplitJoin`]: split-join distance (van Dongen 2000),
1053///   the sum of both [`split_join_distance`]s.
1054/// - [`CommunityComparison::Rand`]: Rand index (1971), fraction of vertex
1055///   pairs on which the two partitions agree.
1056/// - [`CommunityComparison::AdjustedRand`]: Hubert–Arabie adjusted Rand
1057///   index, corrected for chance (may be negative; `NaN` when undefined).
1058///
1059/// Community ids need not be contiguous. Time complexity: O(n log n).
1060///
1061/// Binds [`igraph_compare_communities`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_compare_communities).
1062///
1063/// # Errors
1064/// [`ErrorKind::InvalidValue`] if the lengths
1065/// differ, or for the Rand indices with fewer than two elements.
1066///
1067/// # Examples
1068/// ```
1069/// use igraph::prelude::*;
1070/// use igraph::community::compare_communities;
1071/// let a = [2, 0, 2, 1, 1, 0, 2, 2, 1, 2];
1072/// let b = [1, 1, 2, 1, 1, 0, 2, 2, 0, 2];
1073/// let rand = compare_communities(&a, &b, CommunityComparison::Rand).unwrap();
1074/// assert!((rand - 0.711111).abs() < 1e-6);
1075/// // Relabeling does not matter:
1076/// let vi = compare_communities(&[0, 1], &[1, 0], CommunityComparison::Vi).unwrap();
1077/// assert_eq!(vi, 0.0);
1078/// ```
1079pub fn compare_communities(
1080    comm1: &[i64],
1081    comm2: &[i64],
1082    method: CommunityComparison,
1083) -> Result<f64> {
1084    let c1 = VectorInt::view(comm1);
1085    let c2 = VectorInt::view(comm2);
1086    let mut res = 0.0;
1087    igraph_call!(igraph_compare_communities(
1088        c1.as_ptr(),
1089        c2.as_ptr(),
1090        &mut res,
1091        method.into()
1092    ))?;
1093    Ok(res)
1094}
1095
1096/// The two projection distances between two partitions, whose sum is the
1097/// split-join distance of van Dongen.
1098///
1099/// For each set of the first partition the best matching (maximum overlap)
1100/// set of the second one is found; the first distance is the number of
1101/// elements minus the sum of these overlaps. The second distance is the same
1102/// with the roles swapped. A distance is zero iff the corresponding partition
1103/// is a refinement of the other one. Time complexity: O(n log n).
1104///
1105/// Binds [`igraph_split_join_distance`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_split_join_distance).
1106///
1107/// # Errors
1108/// [`ErrorKind::InvalidValue`] if the lengths differ.
1109///
1110/// # Examples
1111/// ```
1112/// use igraph::community::split_join_distance;
1113/// // Singletons refine the one-block partition:
1114/// assert_eq!(split_join_distance(&[0, 1, 2, 3, 4], &[0; 5]).unwrap(), (0, 4));
1115/// ```
1116pub fn split_join_distance(comm1: &[i64], comm2: &[i64]) -> Result<(i64, i64)> {
1117    let c1 = VectorInt::view(comm1);
1118    let c2 = VectorInt::view(comm2);
1119    let (mut d12, mut d21) = (0, 0);
1120    igraph_call!(igraph_split_join_distance(
1121        c1.as_ptr(),
1122        c2.as_ptr(),
1123        &mut d12,
1124        &mut d21
1125    ))?;
1126    Ok((d12, d21))
1127}
1128
1129// ---------------------------------------------------------------------------
1130// Leading eigenvector callback
1131// ---------------------------------------------------------------------------
1132
1133unsafe extern "C" fn levc_trampoline<F>(
1134    membership: *const igraph_vector_int_t,
1135    comm: igraph_int_t,
1136    eigenvalue: igraph_real_t,
1137    eigenvector: *const igraph_vector_t,
1138    multiplier: igraph_arpack_function_t,
1139    arpack_extra: *mut c_void,
1140    extra: *mut c_void,
1141) -> igraph_error_t
1142where
1143    F: FnMut(&LeadingEigenvectorStep<'_>) -> ControlFlow<()>,
1144{
1145    // The closure runs in a fresh level of igraph's "finally" stack. The
1146    // running `igraph_community_leading_eigenvector` keeps its temporaries
1147    // (adjacency lists, ARPACK storage, ...) on that stack, and igraph's
1148    // error handler frees the current level when a call fails: without a new
1149    // level, a failing igraph call made by the closure (or by
1150    // `LeadingEigenvectorStep::multiply`) would free the objects of the
1151    // computation that is still running, a use-after-free in C.
1152    // SAFETY: plain bookkeeping on igraph's thread-local finally stack; the
1153    // matching EXIT runs below, as `catch_panic` never unwinds.
1154    unsafe { IGRAPH_FINALLY_ENTER() };
1155    let code = catch_panic(|| {
1156        // SAFETY: `extra` is the `&mut F` passed by `community_leading_eigenvector_with`,
1157        // and the vectors are valid for the duration of the callback.
1158        let f = unsafe { &mut *(extra as *mut F) };
1159        let step = LeadingEigenvectorStep {
1160            membership: unsafe { (*membership).as_slice() },
1161            community: comm,
1162            eigenvalue,
1163            eigenvector: unsafe { (*eigenvector).as_slice() },
1164            multiplier,
1165            arpack_extra,
1166        };
1167        match f(&step) {
1168            ControlFlow::Continue(()) => igraph_error_type_t_IGRAPH_SUCCESS,
1169            ControlFlow::Break(()) => igraph_error_type_t_IGRAPH_STOP,
1170        }
1171    });
1172    // SAFETY: closes the level opened above. Every igraph call made by the
1173    // closure has returned, and a failed one has already freed its own
1174    // objects, so the level is empty again.
1175    unsafe { IGRAPH_FINALLY_EXIT() };
1176    code
1177}
1178
1179// ---------------------------------------------------------------------------
1180// Graph methods
1181// ---------------------------------------------------------------------------
1182
1183impl igraph_t {
1184    fn community_check_weights(&self, what: &str, weights: Option<&[f64]>) -> Result<()> {
1185        match weights {
1186            Some(w) => check_len(what, w.len(), self.ecount()),
1187            None => Ok(()),
1188        }
1189    }
1190
1191    fn check_vertex_vec<T>(&self, what: &str, v: Option<&[T]>) -> Result<()> {
1192        match v {
1193            Some(v) => check_len(what, v.len(), self.vcount()),
1194            None => Ok(()),
1195        }
1196    }
1197
1198    /// The coreness (k-core index) of every vertex.
1199    ///
1200    /// The k-core of a graph is its maximal subgraph in which every vertex
1201    /// has degree at least `k`; the coreness of a vertex is the largest `k`
1202    /// such that it belongs to the k-core. For directed graphs `mode` selects
1203    /// in-cores ([`NeighborMode::In`]), out-cores ([`NeighborMode::Out`]) or
1204    /// the undirected version ([`NeighborMode::All`]); it is ignored for
1205    /// undirected graphs. Uses the O(|E|) algorithm of Batagelj and Zaversnik.
1206    ///
1207    /// The coreness of a vertex never exceeds its [degree](Graph::degree);
1208    /// the vertices of coreness `k` or more induce the k-core, which can be
1209    /// extracted with [`Graph::induced_subgraph`].
1210    ///
1211    /// Binds [`igraph_coreness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_coreness).
1212    ///
1213    /// # Examples
1214    /// ```
1215    /// use igraph::prelude::*;
1216    /// // A triangle with a pendant vertex.
1217    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
1218    /// assert_eq!(g.coreness(NeighborMode::All).unwrap(), vec![2, 2, 2, 1]);
1219    /// ```
1220    pub fn coreness(&self, mode: NeighborMode) -> Result<Vec<i64>> {
1221        let mut res = VectorInt::new();
1222        igraph_call!(igraph_coreness(self, &mut res, mode.into()))?;
1223        Ok(res.into())
1224    }
1225
1226    /// The trussness of every edge.
1227    ///
1228    /// A k-truss is a subgraph in which every edge lies in at least `k − 2`
1229    /// triangles of the subgraph; the trussness of an edge is the largest `k`
1230    /// such that it belongs to a k-truss. To get the k-truss, keep the edges
1231    /// with trussness `>= k`. Loops are allowed, multigraphs are not.
1232    /// Time complexity: O(|E|^1.5) (Wang and Cheng, 2012).
1233    ///
1234    /// Every edge has trussness at least 2, and more than 2 exactly when it
1235    /// lies in a triangle (see [`Graph::list_triangles`]); the edges of a
1236    /// `k`-clique (see [`Graph::maximal_cliques`]) have trussness at least `k`.
1237    ///
1238    /// Binds [`igraph_trussness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_trussness).
1239    ///
1240    /// # Errors
1241    /// [`ErrorKind::Unimplemented`] for multigraphs.
1242    ///
1243    /// # Examples
1244    /// ```
1245    /// use igraph::prelude::*;
1246    /// // A 4-clique (every edge in 2 triangles) plus a pendant edge.
1247    /// let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 4)], 5, false)
1248    ///     .unwrap();
1249    /// assert_eq!(g.trussness().unwrap(), vec![4, 4, 4, 4, 4, 4, 2]);
1250    /// ```
1251    pub fn trussness(&self) -> Result<Vec<i64>> {
1252        let mut res = VectorInt::new();
1253        igraph_call!(igraph_trussness(self, &mut res))?;
1254        Ok(res.into())
1255    }
1256
1257    /// The modularity of a partition of the vertices.
1258    ///
1259    /// `Q = 1/(2m) Σ_ij (A_ij − γ k_i k_j / (2m)) δ(c_i, c_j)`, where `m` is
1260    /// the number of edges, `A` the adjacency matrix (with loops counted twice
1261    /// on the diagonal), `k` the degrees, `γ` the `resolution` (1 for the
1262    /// classical definition) and `c` the membership. With `directed = true`
1263    /// on a directed graph the Leicht–Newman version
1264    /// `Q = 1/m Σ_ij (A_ij − γ k^out_i k^in_j / m) δ(c_i, c_j)` is used. With
1265    /// weights, `A`, `k` and `m` are replaced by their weighted counterparts.
1266    /// For graphs without edges the modularity is `NaN`.
1267    ///
1268    /// Community ids need not be contiguous (empty communities are allowed).
1269    /// Time complexity: O(|V| + |E|).
1270    ///
1271    /// For non-negative ids and `resolution = 1`, this is the unnormalized
1272    /// nominal assortativity of the partition, see
1273    /// [`Graph::assortativity_nominal`].
1274    ///
1275    /// Binds [`igraph_modularity`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_modularity).
1276    ///
1277    /// # Errors
1278    /// [`ErrorKind::InvalidValue`] if `membership` or `weights` have a wrong
1279    /// length, a weight is negative, or `resolution < 0`.
1280    ///
1281    /// # Examples
1282    /// ```
1283    /// use igraph::prelude::*;
1284    /// // Two triangles joined by an edge.
1285    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
1286    ///     .unwrap();
1287    /// let q = g.modularity(&[0, 0, 0, 1, 1, 1], None, 1.0, true).unwrap();
1288    /// assert!((q - 5.0 / 14.0).abs() < 1e-12);
1289    /// ```
1290    pub fn modularity(
1291        &self,
1292        membership: &[i64],
1293        weights: Option<&[f64]>,
1294        resolution: f64,
1295        directed: bool,
1296    ) -> Result<f64> {
1297        check_len("the membership vector", membership.len(), self.vcount())?;
1298        self.community_check_weights("the weight vector", weights)?;
1299        let memb = VectorInt::view(membership);
1300        opt_view!(w, weights);
1301        let mut q = 0.0;
1302        igraph_call!(igraph_modularity(
1303            self,
1304            memb.as_ptr(),
1305            w,
1306            resolution,
1307            directed,
1308            &mut q
1309        ))?;
1310        Ok(q)
1311    }
1312
1313    /// The modularity matrix `B_ij = A_ij − γ k_i k_j / (2m)`.
1314    ///
1315    /// For directed graphs (and `directed = true`),
1316    /// `B_ij = A_ij − γ k^out_i k^in_j / m`. Loops of undirected graphs are
1317    /// counted twice in `A`; with weights, the weighted adjacency matrix and
1318    /// strengths are used. When there are no edges the result is undefined
1319    /// (`NaN`s). Then `Q = 1/(2m) Σ_ij B_ij δ(c_i, c_j)`, see [`Graph::modularity`].
1320    /// The adjacency part alone is [`Graph::get_adjacency`] (or this function
1321    /// with `resolution = 0`).
1322    ///
1323    /// Binds [`igraph_modularity_matrix`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_modularity_matrix).
1324    ///
1325    /// # Examples
1326    /// ```
1327    /// use igraph::prelude::*;
1328    /// let triangle = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], 3, false).unwrap();
1329    /// // With resolution 0 the modularity matrix is the adjacency matrix.
1330    /// let b = triangle.modularity_matrix(None, 0.0, false).unwrap();
1331    /// assert_eq!(b.to_rows(), vec![vec![0.0, 1.0, 1.0], vec![1.0, 0.0, 1.0], vec![1.0, 1.0, 0.0]]);
1332    /// ```
1333    pub fn modularity_matrix(
1334        &self,
1335        weights: Option<&[f64]>,
1336        resolution: f64,
1337        directed: bool,
1338    ) -> Result<Matrix> {
1339        self.community_check_weights("the weight vector", weights)?;
1340        opt_view!(w, weights);
1341        let mut res = Matrix::new();
1342        igraph_call!(igraph_modularity_matrix(
1343            self, w, resolution, &mut res, directed
1344        ))?;
1345        Ok(res)
1346    }
1347
1348    /// Louvain community detection: multi-level greedy modularity optimization.
1349    ///
1350    /// Initially each vertex is a community; vertices are then moved, in
1351    /// random order, to the neighboring community that increases modularity
1352    /// the most, until no move helps. Communities are then contracted into
1353    /// single vertices and the process restarts, until there is a single
1354    /// vertex or modularity cannot increase. Higher `resolution` values give
1355    /// more, smaller communities (1 is the classical modularity). Weights
1356    /// must be non-negative. The graph must be undirected. Near linear time
1357    /// on sparse graphs (Blondel et al., 2008).
1358    ///
1359    /// The result contains the final membership and the membership and
1360    /// modularity after each level. For a directed graph, use
1361    /// [`Graph::community_leiden_simple`] (which supports directed modularity)
1362    /// or convert it first with [`Graph::to_undirected`].
1363    ///
1364    /// Binds [`igraph_community_multilevel`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_multilevel).
1365    ///
1366    /// # Errors
1367    /// For directed graphs, negative weights or `resolution < 0`.
1368    ///
1369    /// # Examples
1370    /// ```
1371    /// use igraph::prelude::*;
1372    /// // Two triangles joined by an edge.
1373    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
1374    ///     .unwrap();
1375    /// let res = g.community_multilevel(None, 1.0).unwrap();
1376    /// assert_eq!(res.membership, vec![0, 0, 0, 1, 1, 1]);
1377    /// assert_eq!(res.num_communities(), 2);
1378    /// ```
1379    pub fn community_multilevel(
1380        &self,
1381        weights: Option<&[f64]>,
1382        resolution: f64,
1383    ) -> Result<Multilevel> {
1384        self.community_check_weights("the weight vector", weights)?;
1385        opt_view!(w, weights);
1386        let mut membership = VectorInt::new();
1387        let mut memberships = MatrixInt::new();
1388        let mut modularity = Vector::new();
1389        igraph_call!(igraph_community_multilevel(
1390            self,
1391            w,
1392            resolution,
1393            &mut membership,
1394            &mut memberships,
1395            &mut modularity
1396        ))?;
1397        Ok(Multilevel {
1398            membership: membership.into(),
1399            levels: memberships.to_rows(),
1400            modularities: modularity.into(),
1401        })
1402    }
1403
1404    fn leiden_start(&self, initial: Option<&[i64]>) -> Result<VectorInt> {
1405        match initial {
1406            Some(init) => {
1407                check_len("the initial membership", init.len(), self.vcount())?;
1408                Ok(VectorInt::from_slice(init))
1409            }
1410            None => Ok((0..self.vcount() as i64).collect()),
1411        }
1412    }
1413
1414    /// Leiden community detection with explicit vertex weights.
1415    ///
1416    /// The Leiden algorithm (Traag, Waltman and van Eck, 2019) improves on
1417    /// Louvain by a refinement phase which guarantees well-connected
1418    /// communities. It maximizes
1419    /// `1/(2m) Σ_ij (A_ij − γ n_i n_j) δ(s_i, s_j)` (directed:
1420    /// `1/m Σ_ij (A_ij − γ n^out_i n^in_j) δ(s_i, s_j)`), where `n` are the
1421    /// vertex weights (`vertex_out_weights`, `vertex_in_weights`; `None` means
1422    /// all ones, and `vertex_in_weights` must be `None` for undirected graphs)
1423    /// and `γ` is [`LeidenOptions::resolution`]. With unit vertex weights this
1424    /// is the Constant Potts Model; with degrees as vertex weights and
1425    /// `γ = 1/(2m)` it is modularity (see [`Graph::community_leiden_simple`]
1426    /// for a more convenient interface). Edge weights may be negative.
1427    ///
1428    /// Binds [`igraph_community_leiden`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_leiden).
1429    ///
1430    /// # Errors
1431    /// If a vector has a wrong length.
1432    ///
1433    /// # Examples
1434    /// ```
1435    /// use igraph::prelude::*;
1436    /// use igraph::community::LeidenOptions;
1437    /// let mut edges = vec![(0, 5)];
1438    /// for base in [0, 5] {
1439    ///     for i in 0..5 {
1440    ///         for j in i + 1..5 { edges.push((base + i, base + j)); }
1441    ///     }
1442    /// }
1443    /// let g = Graph::from_edges(&edges, 10, false).unwrap();
1444    /// // Constant Potts Model with resolution 0.05, as in igraph's example.
1445    /// let opts = LeidenOptions::default().with_resolution(0.05).with_iterations(Some(1));
1446    /// let res = g.community_leiden(None, None, None, &opts).unwrap();
1447    /// assert_eq!(res.nb_clusters, 2);
1448    /// assert!((res.quality - 0.8929).abs() < 1e-4);
1449    /// ```
1450    pub fn community_leiden(
1451        &self,
1452        edge_weights: Option<&[f64]>,
1453        vertex_out_weights: Option<&[f64]>,
1454        vertex_in_weights: Option<&[f64]>,
1455        options: &LeidenOptions<'_>,
1456    ) -> Result<Leiden> {
1457        self.community_check_weights("the edge weight vector", edge_weights)?;
1458        self.check_vertex_vec("the vertex out-weight vector", vertex_out_weights)?;
1459        self.check_vertex_vec("the vertex in-weight vector", vertex_in_weights)?;
1460        opt_view!(ew, edge_weights);
1461        opt_view!(vo, vertex_out_weights);
1462        opt_view!(vi, vertex_in_weights);
1463        let mut membership = self.leiden_start(options.initial)?;
1464        let (mut nb, mut quality) = (0, 0.0);
1465        let iterations = options.iterations.map_or(-1, igraph_int_t::from);
1466        igraph_call!(igraph_community_leiden(
1467            self,
1468            ew,
1469            vo,
1470            vi,
1471            options.resolution,
1472            options.beta,
1473            options.initial.is_some(),
1474            iterations,
1475            &mut membership,
1476            &mut nb,
1477            &mut quality
1478        ))?;
1479        Ok(Leiden {
1480            membership: membership.into(),
1481            nb_clusters: nb as usize,
1482            quality,
1483        })
1484    }
1485
1486    /// Leiden community detection optimizing a chosen objective function.
1487    ///
1488    /// A convenience interface to [`Graph::community_leiden`] which computes
1489    /// suitable vertex weights for [`LeidenObjective::Modularity`] (generalized
1490    /// modularity with resolution `γ`), [`LeidenObjective::Cpm`] (Constant
1491    /// Potts Model) or [`LeidenObjective::ErdosRenyi`]. Works on directed and
1492    /// undirected graphs. The reported quality is the value of the chosen
1493    /// objective. Near linear time on sparse graphs.
1494    ///
1495    /// Binds [`igraph_community_leiden_simple`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_leiden_simple).
1496    ///
1497    /// # Errors
1498    /// For negative weights with the modularity or ER objectives, or vectors of
1499    /// wrong length.
1500    ///
1501    /// # Examples
1502    /// ```
1503    /// use igraph::prelude::*;
1504    /// use igraph::community::{LeidenObjective, LeidenOptions};
1505    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
1506    ///     .unwrap();
1507    /// rng::seed(1).unwrap();
1508    /// let res = g
1509    ///     .community_leiden_simple(None, LeidenObjective::Modularity, &LeidenOptions::default())
1510    ///     .unwrap();
1511    /// assert_eq!(res.nb_clusters, 2);
1512    /// let q = g.modularity(&res.membership, None, 1.0, true).unwrap();
1513    /// assert!((res.quality - q).abs() < 1e-12);
1514    /// ```
1515    pub fn community_leiden_simple(
1516        &self,
1517        weights: Option<&[f64]>,
1518        objective: LeidenObjective,
1519        options: &LeidenOptions<'_>,
1520    ) -> Result<Leiden> {
1521        self.community_check_weights("the weight vector", weights)?;
1522        opt_view!(w, weights);
1523        let mut membership = self.leiden_start(options.initial)?;
1524        let (mut nb, mut quality) = (0, 0.0);
1525        let iterations = options.iterations.map_or(-1, igraph_int_t::from);
1526        igraph_call!(igraph_community_leiden_simple(
1527            self,
1528            w,
1529            objective.into(),
1530            options.resolution,
1531            options.beta,
1532            options.initial.is_some(),
1533            iterations,
1534            &mut membership,
1535            &mut nb,
1536            &mut quality
1537        ))?;
1538        Ok(Leiden {
1539            membership: membership.into(),
1540            nb_clusters: nb as usize,
1541            quality,
1542        })
1543    }
1544
1545    /// Greedy agglomerative modularity optimization (Clauset, Newman and Moore).
1546    ///
1547    /// Starting from singletons, the pair of communities whose merge increases
1548    /// modularity the most is merged repeatedly, building a full dendrogram
1549    /// (with the improvements of Wakita and Tsurumi). The returned
1550    /// [`Dendrogram`] contains the merges, the modularity before and after
1551    /// each merge, and the membership with the highest modularity. The graph
1552    /// must not have multi-edges; weights must be non-negative.
1553    /// Time complexity: O(|E| + |V| log²|V|) typically.
1554    ///
1555    /// Binds [`igraph_community_fastgreedy`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_fastgreedy).
1556    ///
1557    /// # Errors
1558    /// For multigraphs (merge the parallel edges first with
1559    /// [`Graph::simplify`], see also [`Graph::has_multiple`]) or invalid
1560    /// weights.
1561    ///
1562    /// # Examples
1563    /// ```
1564    /// use igraph::prelude::*;
1565    /// // The example of igraph's documentation.
1566    /// let g = Graph::from_edges(
1567    ///     &[(0, 1), (1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)], 6, false).unwrap();
1568    /// let weights = [10.0, 10.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
1569    /// let d = g.community_fastgreedy(Some(&weights)).unwrap();
1570    /// assert_eq!(d.merges, vec![(1, 0), (2, 6), (3, 4), (8, 5), (9, 7)]);
1571    /// ```
1572    pub fn community_fastgreedy(&self, weights: Option<&[f64]>) -> Result<Dendrogram> {
1573        self.community_check_weights("the weight vector", weights)?;
1574        opt_view!(w, weights);
1575        let mut merges = MatrixInt::new();
1576        let mut modularity = Vector::new();
1577        let mut membership = VectorInt::new();
1578        igraph_call!(igraph_community_fastgreedy(
1579            self,
1580            w,
1581            &mut merges,
1582            &mut modularity,
1583            &mut membership
1584        ))?;
1585        Ok(Dendrogram {
1586            num_vertices: self.vcount(),
1587            merges: matrix_to_merges(&merges),
1588            modularity: modularity.into(),
1589            membership: membership.into(),
1590        })
1591    }
1592
1593    /// Walktrap community detection based on short random walks (Pons and Latapy).
1594    ///
1595    /// Vertex similarity is measured by random walks of length `steps`
1596    /// (typically 3–8, 4 or 5 being a reasonable default); communities are
1597    /// merged agglomeratively (Ward's method). Edge directions are ignored;
1598    /// weights must be positive. Isolated vertices are allowed. Time
1599    /// complexity: O(|V|² log|V|) typically.
1600    ///
1601    /// Binds [`igraph_community_walktrap`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_walktrap).
1602    ///
1603    /// # Examples
1604    /// ```
1605    /// use igraph::prelude::*;
1606    /// let triangle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false).unwrap();
1607    /// let d = triangle.community_walktrap(None, 4).unwrap();
1608    /// assert_eq!(d.merges, vec![(1, 2), (0, 3)]);
1609    /// assert_eq!(d.membership, vec![0, 0, 0]);
1610    /// ```
1611    pub fn community_walktrap(&self, weights: Option<&[f64]>, steps: usize) -> Result<Dendrogram> {
1612        self.community_check_weights("the weight vector", weights)?;
1613        opt_view!(w, weights);
1614        let mut merges = MatrixInt::new();
1615        let mut modularity = Vector::new();
1616        let mut membership = VectorInt::new();
1617        igraph_call!(igraph_community_walktrap(
1618            self,
1619            w,
1620            steps as igraph_int_t,
1621            &mut merges,
1622            &mut modularity,
1623            &mut membership
1624        ))?;
1625        Ok(Dendrogram {
1626            num_vertices: self.vcount(),
1627            merges: matrix_to_merges(&merges),
1628            modularity: modularity.into(),
1629            membership: membership.into(),
1630        })
1631    }
1632
1633    /// Girvan–Newman community detection by repeatedly removing the edge with
1634    /// the highest betweenness.
1635    ///
1636    /// Betweenness is recomputed after each removal, until no edges remain;
1637    /// the resulting divisive hierarchy is returned as a dendrogram together
1638    /// with the removal order, the betweenness of each removed edge, the
1639    /// "bridges", the modularity of each division and the best membership.
1640    /// With `weights`, the ratio betweenness / weight decides which edge to
1641    /// remove (strong edges are removed later), and weights are used for
1642    /// modularity. With `lengths`, shortest paths take edge lengths into
1643    /// account. For directed graphs `directed` selects directed betweenness and
1644    /// modularity (splits are into weakly connected components).
1645    /// The dendrogram is computed with [`Graph::community_eb_get_merges`], so
1646    /// the order of the two ids within a merge may differ from igraph's
1647    /// output when modularity and membership are not requested.
1648    /// Time complexity: O(|V| |E|²).
1649    ///
1650    /// The first removed edge is the one with the highest
1651    /// [`Graph::edge_betweenness`] (divided by its weight) in the original graph.
1652    ///
1653    /// Binds [`igraph_community_edge_betweenness`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_edge_betweenness).
1654    ///
1655    /// # Errors
1656    /// [`ErrorKind::InvalidValue`] if `weights` or `lengths` have a wrong
1657    /// length or contain invalid values.
1658    ///
1659    /// # Examples
1660    /// ```
1661    /// use igraph::prelude::*;
1662    /// // The example of igraph's documentation.
1663    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (0, 3), (1, 3), (1, 4)], 5, false).unwrap();
1664    /// let weights = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1665    /// let res = g.community_edge_betweenness(Some(&weights), None, false).unwrap();
1666    /// assert_eq!(res.removed_edges, vec![0, 1, 3, 4, 2, 5]);
1667    /// assert_eq!(res.edge_betweenness, vec![2.0, 3.5, 6.0, 2.0, 1.0, 1.0]);
1668    /// assert_eq!(res.merges, vec![(4, 1), (2, 0), (3, 5), (7, 6)]);
1669    /// assert_eq!(res.bridges, vec![5, 4, 3, 2]);
1670    /// ```
1671    pub fn community_edge_betweenness(
1672        &self,
1673        weights: Option<&[f64]>,
1674        lengths: Option<&[f64]>,
1675        directed: bool,
1676    ) -> Result<EdgeBetweennessCommunities> {
1677        self.community_check_weights("the weight vector", weights)?;
1678        self.community_check_weights("the length vector", lengths)?;
1679        opt_view!(w, weights);
1680        opt_view!(l, lengths);
1681        let mut removed = VectorInt::new();
1682        let mut eb = Vector::new();
1683        let mut merges = MatrixInt::new();
1684        let mut bridges = VectorInt::new();
1685        let mut modularity = Vector::new();
1686        let mut membership = VectorInt::new();
1687        igraph_call!(igraph_community_edge_betweenness(
1688            self,
1689            &mut removed,
1690            &mut eb,
1691            &mut merges,
1692            &mut bridges,
1693            &mut modularity,
1694            &mut membership,
1695            directed,
1696            w,
1697            l
1698        ))?;
1699        Ok(EdgeBetweennessCommunities {
1700            removed_edges: removed.into(),
1701            edge_betweenness: eb.into(),
1702            merges: matrix_to_merges(&merges),
1703            bridges: bridges.into(),
1704            modularity: modularity.into(),
1705            membership: membership.into(),
1706        })
1707    }
1708
1709    /// Builds the dendrogram of a sequence of edge removals.
1710    ///
1711    /// Given an order in which *all* the edges are removed (e.g.
1712    /// [`EdgeBetweennessCommunities::removed_edges`], but any order works),
1713    /// the removal process is replayed backwards and each time two components
1714    /// get connected a merge is recorded (component ids below `vcount` are
1715    /// vertices, merged components are numbered from `vcount`). Modularity
1716    /// (weighted if `weights` is given, directed if `directed`) is computed
1717    /// for each division, and the best membership is returned.
1718    /// Time complexity: O(|E| + |V| log|V|).
1719    ///
1720    /// Binds [`igraph_community_eb_get_merges`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_eb_get_merges).
1721    ///
1722    /// # Errors
1723    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge id in `edges`, and
1724    /// [`ErrorKind::InvalidValue`] if `edges` is otherwise not a permutation
1725    /// of the edge ids.
1726    ///
1727    /// # Examples
1728    /// ```
1729    /// use igraph::prelude::*;
1730    /// let path = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1731    /// // Remove the middle edge first, then the outer ones.
1732    /// let res = path.community_eb_get_merges(false, &[1, 0, 2], None).unwrap();
1733    /// assert_eq!(res.dendrogram.merges, vec![(3, 2), (1, 0), (4, 5)]);
1734    /// assert_eq!(res.dendrogram.membership, vec![0, 0, 1, 1]);
1735    /// ```
1736    pub fn community_eb_get_merges(
1737        &self,
1738        directed: bool,
1739        edges: &[EdgeId],
1740        weights: Option<&[f64]>,
1741    ) -> Result<EdgeRemovalMerges> {
1742        self.community_check_weights("the weight vector", weights)?;
1743        // igraph (1.0.0 and 1.0.1) only checks that the ids are valid and
1744        // that there are enough of them: with a repeated (hence a missing) edge fewer merges happen
1745        // than the rows it allocates, and the rest of its outputs would be
1746        // left uninitialized. Require a permutation of the edge ids.
1747        let m = self.ecount();
1748        check_len("the edge removal order", edges.len(), m)?;
1749        let mut seen = vec![false; m];
1750        for &e in edges {
1751            let Some(idx) = usize::try_from(e).ok().filter(|&e| e < m) else {
1752                return Err(Error::new(
1753                    ErrorKind::InvalidEdgeId,
1754                    format!("invalid edge id {e} in the edge removal order"),
1755                ));
1756            };
1757            if std::mem::replace(&mut seen[idx], true) {
1758                return Err(Error::invalid(format!(
1759                    "edge {e} appears more than once in the edge removal order"
1760                )));
1761            }
1762        }
1763        let e = VectorInt::view(edges);
1764        opt_view!(w, weights);
1765        let mut merges = MatrixInt::new();
1766        let mut bridges = VectorInt::new();
1767        let mut modularity = Vector::new();
1768        let mut membership = VectorInt::new();
1769        igraph_call!(igraph_community_eb_get_merges(
1770            self,
1771            directed,
1772            e.as_ptr(),
1773            w,
1774            &mut merges,
1775            &mut bridges,
1776            &mut modularity,
1777            &mut membership
1778        ))?;
1779        Ok(EdgeRemovalMerges {
1780            dendrogram: Dendrogram {
1781                num_vertices: self.vcount(),
1782                merges: matrix_to_merges(&merges),
1783                modularity: modularity.into(),
1784                membership: membership.into(),
1785            },
1786            bridges: bridges.into(),
1787        })
1788    }
1789
1790    /// Newman's leading eigenvector method (recursive spectral bisection).
1791    ///
1792    /// Starting from the connected components (see
1793    /// [`Graph::connected_components`]) or from `start`, each
1794    /// community is split in two according to the signs of the leading
1795    /// eigenvector of its generalized modularity matrix, as long as this
1796    /// increases modularity, performing at most `steps` splits (`None`: as
1797    /// many as possible). The initial division into `c` components (or `c`
1798    /// start communities) counts as `c − 1` steps, so at most
1799    /// `max(steps, c − 1) + 1` communities are returned. Start community ids
1800    /// must lie in `0..vcount`; communities with at most two vertices are
1801    /// never split. Edge directions are ignored. ARPACK is used with
1802    /// igraph's default options. Time complexity: O(|E| + |V|² steps).
1803    ///
1804    /// Binds [`igraph_community_leading_eigenvector`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_leading_eigenvector).
1805    ///
1806    /// # Errors
1807    /// [`ErrorKind::InvalidValue`] if `start` or `weights` have a wrong
1808    /// length, `start` contains ids outside `0..vcount`, or the weights
1809    /// contain `NaN` or infinite values or their absolute sum exceeds `1e150`
1810    /// (igraph does not check these, and ARPACK would abort the process);
1811    /// [`ErrorKind::Arpack`] if ARPACK fails.
1812    ///
1813    /// # Examples
1814    /// ```
1815    /// use igraph::prelude::*;
1816    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
1817    ///     .unwrap();
1818    /// let res = g.community_leading_eigenvector(None, None, None).unwrap();
1819    /// assert_eq!(res.num_communities(), 2);
1820    /// assert!((res.modularity - 5.0 / 14.0).abs() < 1e-12);
1821    /// ```
1822    pub fn community_leading_eigenvector(
1823        &self,
1824        weights: Option<&[f64]>,
1825        steps: Option<usize>,
1826        start: Option<&[i64]>,
1827    ) -> Result<LeadingEigenvector> {
1828        self.leading_eigenvector_impl(
1829            weights,
1830            steps,
1831            start,
1832            None::<fn(&LeadingEigenvectorStep<'_>) -> _>,
1833        )
1834    }
1835
1836    /// Like [`Graph::community_leading_eigenvector`], calling `callback` after
1837    /// each eigenvector computation.
1838    ///
1839    /// The callback receives a [`LeadingEigenvectorStep`] describing the
1840    /// community being split, the eigenvalue and eigenvector, and can compute
1841    /// products with the community's modularity matrix. Returning
1842    /// [`ControlFlow::Break`] stops the algorithm (the partition found so far
1843    /// is returned); a panic in the callback is propagated.
1844    ///
1845    /// Binds [`igraph_community_leading_eigenvector`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_leading_eigenvector)
1846    /// with a callback.
1847    ///
1848    /// # Examples
1849    /// ```
1850    /// use igraph::prelude::*;
1851    /// use std::ops::ControlFlow;
1852    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
1853    ///     .unwrap();
1854    /// let mut eigenvalues = vec![];
1855    /// let res = g
1856    ///     .community_leading_eigenvector_with(None, None, None, |step| {
1857    ///         eigenvalues.push(step.eigenvalue());
1858    ///         // B v = λ v
1859    ///         let bv = step.multiply(step.eigenvector()).unwrap();
1860    ///         for (x, v) in bv.iter().zip(step.eigenvector()) {
1861    ///             assert!((x - step.eigenvalue() * v).abs() < 1e-6);
1862    ///         }
1863    ///         ControlFlow::Continue(())
1864    ///     })
1865    ///     .unwrap();
1866    /// assert_eq!(eigenvalues, res.eigenvalues);
1867    /// ```
1868    pub fn community_leading_eigenvector_with<F>(
1869        &self,
1870        weights: Option<&[f64]>,
1871        steps: Option<usize>,
1872        start: Option<&[i64]>,
1873        callback: F,
1874    ) -> Result<LeadingEigenvector>
1875    where
1876        F: FnMut(&LeadingEigenvectorStep<'_>) -> ControlFlow<()>,
1877    {
1878        self.leading_eigenvector_impl(weights, steps, start, Some(callback))
1879    }
1880
1881    fn leading_eigenvector_impl<F>(
1882        &self,
1883        weights: Option<&[f64]>,
1884        steps: Option<usize>,
1885        start: Option<&[i64]>,
1886        mut callback: Option<F>,
1887    ) -> Result<LeadingEigenvector>
1888    where
1889        F: FnMut(&LeadingEigenvectorStep<'_>) -> ControlFlow<()>,
1890    {
1891        self.community_check_weights("the weight vector", weights)?;
1892        // igraph (1.0.0 and 1.0.1) only checks the length of the weights:
1893        // NaN, infinite or so large weights that the modularity matrix-vector
1894        // products overflow make ARPACK abort the whole process.
1895        check_finite("the weight vector", weights)?;
1896        if let Some(w) = weights
1897            && abs_sum(w) > MAX_ABS_WEIGHT_SUM
1898        {
1899            return Err(Error::invalid(format!(
1900                "the sum of the absolute weights must be at most {MAX_ABS_WEIGHT_SUM:e}, got {:e}",
1901                abs_sum(w)
1902            )));
1903        }
1904        self.check_vertex_vec("the start membership", start)?;
1905        // igraph (1.0.0 and 1.0.1) only warns about community ids >= |V|, but
1906        // then indexes a |V|-long work vector with them when building the
1907        // merges: reject them.
1908        let n = self.vcount() as i64;
1909        if let Some(s) = start
1910            && let Some(&c) = s.iter().find(|&&c| !(0..n).contains(&c))
1911        {
1912            return Err(Error::invalid(format!(
1913                "the start membership contains the community id {c}, \
1914                 ids must be in 0..{n}"
1915            )));
1916        }
1917        // igraph takes the maximum of the start vector, which is undefined
1918        // for the null graph: there is nothing to start from anyway.
1919        let start = start.filter(|s| !s.is_empty());
1920        opt_view!(w, weights);
1921        let mut merges = MatrixInt::new();
1922        let mut membership = start.map_or_else(VectorInt::new, VectorInt::from_slice);
1923        let mut modularity = 0.0;
1924        let mut eigenvalues = Vector::new();
1925        let mut eigenvectors = VectorList::new();
1926        let mut history = VectorInt::new();
1927        let steps = steps.map_or(-1, |s| s.min(igraph_int_t::MAX as usize) as igraph_int_t);
1928        let (cb, extra): (igraph_community_leading_eigenvector_callback_t, *mut c_void) =
1929            match callback.as_mut() {
1930                Some(f) => (Some(levc_trampoline::<F>), f as *mut F as *mut c_void),
1931                None => (None, ptr::null_mut()),
1932            };
1933        // ARPACK keeps thread-local state: refuse to nest it.
1934        let _arpack = crate::linalg::ArpackGuard::enter()?;
1935        igraph_call!(igraph_community_leading_eigenvector(
1936            self,
1937            w,
1938            &mut merges,
1939            &mut membership,
1940            steps,
1941            ptr::null_mut(),
1942            &mut modularity,
1943            start.is_some(),
1944            &mut eigenvalues,
1945            &mut eigenvectors,
1946            &mut history,
1947            cb,
1948            extra
1949        ))?;
1950        Ok(LeadingEigenvector {
1951            membership: membership.into(),
1952            merges: matrix_to_merges(&merges),
1953            modularity,
1954            eigenvalues: eigenvalues.into(),
1955            eigenvectors: eigenvectors.to_vecs(),
1956            history: decode_history(&history),
1957        })
1958    }
1959
1960    /// Spinglass community detection (Reichardt and Bornholdt).
1961    ///
1962    /// Finds communities as the ground state of a Potts spin glass by
1963    /// simulated annealing, with at most [`SpinglassOptions::spins`]
1964    /// communities. The `Neg` implementation (Traag and Bruggeman) supports
1965    /// negative weights. Edge directions are ignored. The graph must be
1966    /// connected (check with [`Graph::is_connected`]; cluster each
1967    /// component separately otherwise). The result is random: seed the
1968    /// thread's generator with [`rng::seed`](crate::rng::seed) for
1969    /// reproducibility.
1970    ///
1971    /// Binds [`igraph_community_spinglass`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_spinglass).
1972    ///
1973    /// # Errors
1974    /// [`ErrorKind::InvalidValue`] for disconnected graphs, invalid
1975    /// parameters or invalid weights. Beyond igraph's own checks (e.g. the
1976    /// temperatures must be both zero, or both positive with the starting one
1977    /// larger), the following are rejected on the Rust side, as igraph would
1978    /// loop forever or overflow on them: `NaN` or infinite weights, or
1979    /// weights whose absolute sum exceeds `1e150`; a `gamma` outside
1980    /// `[0, 1e150]` (or `NaN`); with the `Neg` implementation, a
1981    /// `gamma_minus` of magnitude above `1e150` (or `NaN`); a cooling factor
1982    /// outside `[0, 1)` (or `NaN`); temperatures outside `[0, 1e300]`; and a
1983    /// number of spins outside `2..=i32::MAX`.
1984    ///
1985    /// # Examples
1986    /// ```
1987    /// use igraph::prelude::*;
1988    /// use igraph::community::SpinglassOptions;
1989    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
1990    ///     .unwrap();
1991    /// rng::seed(42).unwrap();
1992    /// let res = g.community_spinglass(None, &SpinglassOptions::default()).unwrap();
1993    /// assert_eq!(res.num_communities(), 2);
1994    /// assert!((res.modularity - 5.0 / 14.0).abs() < 1e-9);
1995    /// ```
1996    pub fn community_spinglass(
1997        &self,
1998        weights: Option<&[f64]>,
1999        options: &SpinglassOptions,
2000    ) -> Result<Spinglass> {
2001        self.community_check_weights("the weight vector", weights)?;
2002        check_spinglass(weights, options, true)?;
2003        opt_view!(w, weights);
2004        let (mut modularity, mut temperature) = (0.0, 0.0);
2005        let mut membership = VectorInt::new();
2006        let mut csize = VectorInt::new();
2007        igraph_call!(igraph_community_spinglass(
2008            self,
2009            w,
2010            &mut modularity,
2011            &mut temperature,
2012            &mut membership,
2013            &mut csize,
2014            options.spins as igraph_int_t,
2015            options.parallel_update,
2016            options.start_temperature,
2017            options.stop_temperature,
2018            options.cooling_factor,
2019            options.update_rule.into(),
2020            options.gamma,
2021            options.implementation.into(),
2022            options.gamma_minus
2023        ))?;
2024        Ok(Spinglass {
2025            membership: membership.into(),
2026            csize: csize.into(),
2027            modularity,
2028            temperature,
2029        })
2030    }
2031
2032    /// The spinglass community of a single vertex, without computing the
2033    /// whole partition.
2034    ///
2035    /// Uses [`SpinglassOptions::spins`], [`update_rule`](SpinglassOptions::update_rule)
2036    /// and [`gamma`](SpinglassOptions::gamma) (the other options are ignored).
2037    /// Also returns the cohesion and adhesion indices of the community and the
2038    /// number (or weight) of its inner and outer edges. The graph must be
2039    /// connected.
2040    ///
2041    /// Binds [`igraph_community_spinglass_single`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_spinglass_single).
2042    ///
2043    /// # Errors
2044    /// [`ErrorKind::InvalidVertexId`] if `vertex` is not a vertex of the
2045    /// graph, and [`ErrorKind::InvalidValue`] for disconnected graphs or
2046    /// invalid parameters: spins outside `2..=i32::MAX`, a `gamma` outside
2047    /// `[0, 1e150]` (or `NaN`), `NaN` or infinite weights (or weights whose
2048    /// absolute sum exceeds `1e150`).
2049    ///
2050    /// # Examples
2051    /// ```
2052    /// use igraph::prelude::*;
2053    /// use igraph::community::SpinglassOptions;
2054    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2055    ///     .unwrap();
2056    /// rng::seed(42).unwrap();
2057    /// let res = g.community_spinglass_single(None, 0, &SpinglassOptions::default()).unwrap();
2058    /// let mut community = res.community.clone();
2059    /// community.sort();
2060    /// assert_eq!(community, vec![0, 1, 2]);
2061    /// // Three edges inside the triangle, one (the bridge) leaving it.
2062    /// assert_eq!((res.inner_links, res.outer_links), (3.0, 1.0));
2063    /// ```
2064    pub fn community_spinglass_single(
2065        &self,
2066        weights: Option<&[f64]>,
2067        vertex: VertexId,
2068        options: &SpinglassOptions,
2069    ) -> Result<SpinglassSingle> {
2070        self.community_check_weights("the weight vector", weights)?;
2071        check_spinglass(weights, options, false)?;
2072        // igraph (1.0.0 and 1.0.1) accepts `vertex == vcount` (an off-by-one
2073        // in its check) and then silently returns an empty community.
2074        if !(0..self.vcount() as VertexId).contains(&vertex) {
2075            return Err(Error::new(
2076                ErrorKind::InvalidVertexId,
2077                format!(
2078                    "invalid vertex id {vertex} for a graph with {} vertices",
2079                    self.vcount()
2080                ),
2081            ));
2082        }
2083        opt_view!(w, weights);
2084        let mut community = VectorInt::new();
2085        let (mut cohesion, mut adhesion, mut inner, mut outer) = (0.0, 0.0, 0.0, 0.0);
2086        igraph_call!(igraph_community_spinglass_single(
2087            self,
2088            w,
2089            vertex,
2090            &mut community,
2091            &mut cohesion,
2092            &mut adhesion,
2093            &mut inner,
2094            &mut outer,
2095            options.spins as igraph_int_t,
2096            options.update_rule.into(),
2097            options.gamma
2098        ))?;
2099        Ok(SpinglassSingle {
2100            community: community.into(),
2101            cohesion,
2102            adhesion,
2103            inner_links: inner,
2104            outer_links: outer,
2105        })
2106    }
2107
2108    /// Label propagation community detection (Raghavan, Albert and Kumara;
2109    /// fast variant of Traag and Šubelj).
2110    ///
2111    /// Every vertex repeatedly adopts the label that is dominant (highest
2112    /// total edge weight) among its neighbors, until all labels are dominant.
2113    /// See [`LabelPropagationOptions`] for directed propagation, initial and
2114    /// fixed labels and the variants. Weights must be non-negative. Ties are
2115    /// broken at random, so seed the thread's generator for reproducible
2116    /// results. In directed graphs, labels circulate freely only within
2117    /// strongly connected components (see [`Graph::connected_components`]
2118    /// with [`Connectedness::Strong`](crate::constants::Connectedness::Strong)).
2119    /// Unlabeled vertices unreachable from labeled ones are labeled in an
2120    /// extra step (each such undirected component gets its own label).
2121    /// Time complexity: O(|V| + |E|) per iteration.
2122    ///
2123    /// Binds [`igraph_community_label_propagation`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_label_propagation).
2124    ///
2125    /// # Errors
2126    /// [`ErrorKind::InvalidValue`] for vectors of wrong length, negative or
2127    /// `NaN` weights, or initial labels outside `0..vcount` (negative ones
2128    /// excepted).
2129    ///
2130    /// # Examples
2131    /// ```
2132    /// use igraph::prelude::*;
2133    /// use igraph::community::LabelPropagationOptions;
2134    /// let path = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], 5, false).unwrap();
2135    /// // Fix the labels of both ends; the others are unlabeled.
2136    /// let initial = [0, -1, -1, -1, 1];
2137    /// let fixed = [true, false, false, false, true];
2138    /// let opts = LabelPropagationOptions { initial: Some(&initial), fixed: Some(&fixed),
2139    ///                                      ..Default::default() };
2140    /// let m = path.community_label_propagation(None, &opts).unwrap();
2141    /// // The fixed vertices keep distinct labels, and nobody gets a third one
2142    /// // (ties are broken at random, so the boundary may vary).
2143    /// assert_ne!(m[0], m[4]);
2144    /// assert!(m.iter().all(|&l| l == m[0] || l == m[4]));
2145    /// ```
2146    pub fn community_label_propagation(
2147        &self,
2148        weights: Option<&[f64]>,
2149        options: &LabelPropagationOptions<'_>,
2150    ) -> Result<Vec<i64>> {
2151        self.community_check_weights("the weight vector", weights)?;
2152        self.check_vertex_vec("the initial labels", options.initial)?;
2153        self.check_vertex_vec("the fixed labels", options.fixed)?;
2154        // igraph (1.0.0 and 1.0.1) rejects labels above |V| but accepts
2155        // |V| itself (an off-by-one), then indexes |V|-long work vectors
2156        // with it: reject every label outside 0..|V| here.
2157        let n = self.vcount() as i64;
2158        if let Some(&l) = options
2159            .initial
2160            .and_then(|init| init.iter().find(|&&l| l >= n))
2161        {
2162            return Err(Error::invalid(format!(
2163                "the initial label {l} is out of range, labels must be in 0..{n} \
2164                 (or negative for unlabeled vertices)"
2165            )));
2166        }
2167        // igraph (1.0.0 and 1.0.1) takes the maximum of the initial labels,
2168        // which aborts the process for the null graph (an empty vector): the
2169        // labels of zero vertices carry no information, so drop them.
2170        let (initial, fixed) = if n == 0 {
2171            (None, None)
2172        } else {
2173            (options.initial, options.fixed)
2174        };
2175        opt_view!(w, weights);
2176        let init = initial.map(VectorInt::view);
2177        let init = init.as_ref().map_or(ptr::null(), |v| v.as_ptr());
2178        let fixed = fixed.map(VectorBool::view);
2179        let fixed = fixed.as_ref().map_or(ptr::null(), |v| v.as_ptr());
2180        let mut membership = VectorInt::new();
2181        igraph_call!(igraph_community_label_propagation(
2182            self,
2183            &mut membership,
2184            options.mode.into(),
2185            w,
2186            init,
2187            fixed,
2188            options.variant.into()
2189        ))?;
2190        Ok(membership.into())
2191    }
2192
2193    /// Infomap community detection: minimizes the map equation, the expected
2194    /// description length of a random walk (Rosvall and Bergstrom).
2195    ///
2196    /// The random walker follows out-edges proportionally to `edge_weights`
2197    /// (non-negative) and teleports with probability 0.15 to a vertex chosen
2198    /// proportionally to `vertex_weights` (positive). Edge directions are
2199    /// taken into account. The best of [`InfomapOptions::trials`] attempts is
2200    /// returned, with its code length (in bits). The attempts are random.
2201    ///
2202    /// Binds [`igraph_community_infomap`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_infomap).
2203    ///
2204    /// # Errors
2205    /// [`ErrorKind::InvalidValue`] for weight vectors of wrong length or
2206    /// invalid weights or trials, and [`ErrorKind::Unimplemented`] if igraph
2207    /// was built without Infomap support (as documented since igraph 1.0.1).
2208    ///
2209    /// # Examples
2210    /// ```
2211    /// use igraph::prelude::*;
2212    /// use igraph::community::InfomapOptions;
2213    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2214    ///     .unwrap();
2215    /// rng::seed(7).unwrap();
2216    /// let res = g.community_infomap(None, None, &InfomapOptions::default()).unwrap();
2217    /// assert_eq!(res.num_communities(), 2);
2218    /// assert!(res.codelength > 0.0);
2219    /// ```
2220    pub fn community_infomap(
2221        &self,
2222        edge_weights: Option<&[f64]>,
2223        vertex_weights: Option<&[f64]>,
2224        options: &InfomapOptions,
2225    ) -> Result<Infomap> {
2226        self.community_check_weights("the edge weight vector", edge_weights)?;
2227        self.check_vertex_vec("the vertex weight vector", vertex_weights)?;
2228        // igraph stores the number of trials in an `unsigned int`: larger
2229        // values would be silently truncated (2^32 trials becoming 0 trials
2230        // and a meaningless all-zero membership).
2231        let trials = u32::try_from(options.trials)
2232            .ok()
2233            .filter(|&t| t >= 1)
2234            .ok_or_else(|| {
2235                Error::invalid(format!(
2236                    "the number of Infomap trials must be in 1..={}, got {}",
2237                    u32::MAX,
2238                    options.trials
2239                ))
2240            })?;
2241        opt_view!(ew, edge_weights);
2242        opt_view!(vw, vertex_weights);
2243        let mut membership = VectorInt::new();
2244        let mut codelength = 0.0;
2245        igraph_call!(igraph_community_infomap(
2246            self,
2247            ew,
2248            vw,
2249            igraph_int_t::from(trials),
2250            options.regularized,
2251            options.regularization_strength,
2252            &mut membership,
2253            &mut codelength
2254        ))?;
2255        Ok(Infomap {
2256            membership: membership.into(),
2257            codelength,
2258        })
2259    }
2260
2261    /// Fluid communities: `k` "fluids" expand and contract on the graph
2262    /// until they reach an equilibrium (Parés et al., 2017).
2263    ///
2264    /// The graph must be simple and connected; edge directions are ignored,
2265    /// weights are not supported. `k` must be positive and at most the number
2266    /// of vertices. The result is random (seed the thread's generator for
2267    /// reproducibility). Time complexity: O(|E|).
2268    ///
2269    /// Binds [`igraph_community_fluid_communities`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_fluid_communities).
2270    ///
2271    /// # Errors
2272    /// For non-simple graphs (see [`Graph::is_simple`]), disconnected
2273    /// graphs (see [`Graph::is_connected`]) or an invalid `k`.
2274    ///
2275    /// # Examples
2276    /// ```
2277    /// use igraph::prelude::*;
2278    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2279    ///     .unwrap();
2280    /// rng::seed(3).unwrap();
2281    /// let m = g.community_fluid_communities(2).unwrap();
2282    /// assert_eq!(m.iter().filter(|&&c| c == m[0]).count(), 3);
2283    /// ```
2284    pub fn community_fluid_communities(&self, k: usize) -> Result<Vec<i64>> {
2285        let mut membership = VectorInt::new();
2286        igraph_call!(igraph_community_fluid_communities(
2287            self,
2288            k as igraph_int_t,
2289            &mut membership
2290        ))?;
2291        Ok(membership.into())
2292    }
2293
2294    /// Voronoi community detection (Deritei et al.; Molnár et al.). *Experimental in igraph.*
2295    ///
2296    /// Generator vertices are chosen as those with the largest local relative
2297    /// density `s m / (m + k)` within `radius` (`s` being the strength of the
2298    /// vertex, `m` the number of edges within its first-order neighborhood and
2299    /// `k` the number of edges with a single endpoint in it), and every vertex
2300    /// is assigned to its closest generator (ties broken at random) using the
2301    /// edge `lengths` divided by the edge clustering coefficient
2302    /// ([`Graph::ecc`]), as in [`Graph::voronoi`]. `weights` are used to
2303    /// select the generators and compute modularity. `mode` selects distances from ([`NeighborMode::Out`])
2304    /// or to ([`NeighborMode::In`]) the generators in directed graphs. With
2305    /// `radius = None` the radius maximizing modularity is chosen automatically;
2306    /// an explicit radius must be non-negative (larger radii give fewer
2307    /// communities). The graph must be simple.
2308    ///
2309    /// Binds [`igraph_community_voronoi`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_voronoi).
2310    ///
2311    /// # Errors
2312    /// [`ErrorKind::InvalidValue`] for a negative or `NaN` radius, vectors of
2313    /// wrong length, `NaN` or infinite lengths or weights, or non-simple
2314    /// graphs. With `radius = None`, the sum of the lengths times the number
2315    /// of vertices must also be at most `1e150` (igraph 1.0.1 aborts the
2316    /// process when its radius search overflows; this is checked here).
2317    ///
2318    /// # Examples
2319    /// ```
2320    /// use igraph::prelude::*;
2321    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2322    ///     .unwrap();
2323    /// rng::seed(42).unwrap();
2324    /// let res = g.community_voronoi(None, None, NeighborMode::All, None).unwrap();
2325    /// assert_eq!(res.num_communities(), 2);
2326    /// assert_eq!(res.generators.len(), 2);
2327    /// // Each generator lies in its own community, and the two triangles are found.
2328    /// for (c, &v) in res.generators.iter().enumerate() {
2329    ///     assert_eq!(res.membership[v as usize], c as i64);
2330    /// }
2331    /// assert!((res.modularity - 5.0 / 14.0).abs() < 1e-12);
2332    /// ```
2333    pub fn community_voronoi(
2334        &self,
2335        lengths: Option<&[f64]>,
2336        weights: Option<&[f64]>,
2337        mode: NeighborMode,
2338        radius: Option<f64>,
2339    ) -> Result<Voronoi> {
2340        self.community_check_weights("the length vector", lengths)?;
2341        self.community_check_weights("the weight vector", weights)?;
2342        // igraph (1.0.0 and 1.0.1) accepts infinite lengths and weights. With
2343        // an automatic radius, infinite (or so large that shortest path
2344        // lengths overflow) lengths make its radius optimizer abort the whole
2345        // process on an assertion. Lengths are scaled by `1 / ECC <= |V|`
2346        // internally, so shortest paths are at most `|V| * sum(lengths)`.
2347        check_finite("the length vector", lengths)?;
2348        check_finite("the weight vector", weights)?;
2349        if radius.is_none()
2350            && let Some(l) = lengths
2351            && abs_sum(l) * self.vcount() as f64 > MAX_ABS_WEIGHT_SUM
2352        {
2353            return Err(Error::invalid(format!(
2354                "with an automatic radius, the sum of the edge lengths times the number \
2355                 of vertices must be at most {MAX_ABS_WEIGHT_SUM:e}"
2356            )));
2357        }
2358        let radius = match radius {
2359            None => -1.0,
2360            Some(r) if r >= 0.0 => r,
2361            Some(r) => {
2362                return Err(Error::invalid(format!(
2363                    "the Voronoi radius must be non-negative, got {r}"
2364                )));
2365            }
2366        };
2367        opt_view!(l, lengths);
2368        opt_view!(w, weights);
2369        let mut membership = VectorInt::new();
2370        let mut generators = VectorInt::new();
2371        let mut modularity = 0.0;
2372        self.with_fresh_multi_cache(|| {
2373            igraph_call!(igraph_community_voronoi(
2374                self,
2375                &mut membership,
2376                &mut generators,
2377                &mut modularity,
2378                l,
2379                w,
2380                mode.into(),
2381                radius
2382            ))
2383        })?;
2384        Ok(Voronoi {
2385            membership: membership.into(),
2386            generators: generators.into(),
2387            modularity,
2388        })
2389    }
2390
2391    /// The partition with the highest possible modularity, by integer
2392    /// programming (Brandes et al., 2008) with GLPK.
2393    ///
2394    /// Exact modularity maximization is NP-complete: graphs up to ~50 vertices
2395    /// are fine, a few hundred may be possible. Directed graphs are supported.
2396    /// `resolution` is the `γ` of [`Graph::modularity`].
2397    ///
2398    /// Binds [`igraph_community_optimal_modularity`](https://igraph.org/c/html/latest/igraph-Community.html#igraph_community_optimal_modularity).
2399    ///
2400    /// # Errors
2401    /// [`ErrorKind::Unimplemented`] if igraph
2402    /// was built without GLPK.
2403    ///
2404    /// # Examples
2405    /// ```
2406    /// use igraph::prelude::*;
2407    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2408    ///     .unwrap();
2409    /// match g.community_optimal_modularity(None, 1.0) {
2410    ///     Ok(best) => assert!((best.modularity - 5.0 / 14.0).abs() < 1e-9),
2411    ///     Err(e) => assert_eq!(e.kind(), ErrorKind::Unimplemented), // no GLPK
2412    /// }
2413    /// ```
2414    pub fn community_optimal_modularity(
2415        &self,
2416        weights: Option<&[f64]>,
2417        resolution: f64,
2418    ) -> Result<Clustering> {
2419        self.community_check_weights("the weight vector", weights)?;
2420        opt_view!(w, weights);
2421        let mut modularity = 0.0;
2422        let mut membership = VectorInt::new();
2423        igraph_call!(igraph_community_optimal_modularity(
2424            self,
2425            w,
2426            resolution,
2427            &mut modularity,
2428            &mut membership
2429        ))?;
2430        Ok(Clustering {
2431            membership: membership.into(),
2432            modularity,
2433        })
2434    }
2435}
2436
2437// ---------------------------------------------------------------------------
2438// Hierarchical random graphs
2439// ---------------------------------------------------------------------------
2440
2441/// A hierarchical random graph (HRG) model (`igraph_hrg_t`), after Clauset,
2442/// Moore and Newman.
2443///
2444/// An HRG with `n` leaves (the vertices of the modeled graph) is a binary
2445/// dendrogram with `n − 1` internal nodes, each labeled with a probability
2446/// `p`: two vertices are connected with the probability of their lowest
2447/// common ancestor. Internal node `i` has a left and a right child: a
2448/// non-negative child id is a leaf (vertex), a negative one `-j - 1` is the
2449/// internal node `j`.
2450///
2451/// An `Hrg` is always a valid, complete dendrogram: it is obtained by fitting
2452/// ([`Graph::hrg_fit`]), from the MCMC of [`Graph::hrg_consensus`] and
2453/// [`Graph::hrg_predict`], or from an explicit tree with [`Hrg::create`]. Its
2454/// storage is freed on drop.
2455///
2456/// ```
2457/// use igraph::prelude::*;
2458/// use igraph::community::Hrg;
2459///
2460/// // A root (vertex 0) splitting into leaf 3 and an internal node (1) whose
2461/// // children are leaf 4 and internal node 2 with leaves 5 and 6.
2462/// let tree = Graph::from_edges(&[(0, 3), (0, 1), (1, 4), (1, 2), (2, 5), (2, 6)], 7, true).unwrap();
2463/// let hrg = Hrg::create(&tree, &[1.0, 0.0, 0.0]).unwrap();
2464/// assert_eq!(hrg.size(), 4);
2465/// // Leaf 3 (first leaf, id 0) connects to everyone, the others never connect.
2466/// let sample = hrg.sample().unwrap();
2467/// assert_eq!(sample.edge_list(), vec![(0, 1), (0, 2), (0, 3)]);
2468/// ```
2469pub struct Hrg {
2470    raw: igraph_hrg_t,
2471}
2472
2473// The HRG only owns igraph vectors, which are `Send + Sync`.
2474unsafe impl Send for Hrg {}
2475unsafe impl Sync for Hrg {}
2476
2477impl Hrg {
2478    /// An initialized, placeholder HRG of `n` leaves (only for internal use,
2479    /// as an output argument).
2480    fn placeholder(n: usize) -> Result<Self> {
2481        let mut raw = MaybeUninit::<igraph_hrg_t>::uninit();
2482        igraph_call!(igraph_hrg_init(raw.as_mut_ptr(), n as igraph_int_t))?;
2483        // Dropping `raw` drops its five vectors, like `igraph_hrg_destroy`.
2484        Ok(Self {
2485            raw: unsafe { raw.assume_init() },
2486        })
2487    }
2488
2489    /// Whether the five vectors describe a complete binary dendrogram rooted
2490    /// at internal node 0, which is what igraph's HRG code assumes (in igraph
2491    /// 1.0.0 and 1.0.1 it follows the child indices without any check, and
2492    /// loops forever on cycles).
2493    fn is_valid_dendrogram(&self) -> bool {
2494        let (left, right) = (self.left(), self.right());
2495        let internal = left.len();
2496        let leaves = internal + 1;
2497        if internal == 0
2498            || right.len() != internal
2499            || self.prob().len() != internal
2500            || self.edges().len() != internal
2501            || self.vertices().len() != internal
2502        {
2503            return false;
2504        }
2505        // Each leaf and each non-root internal node is the child of exactly
2506        // one internal node.
2507        let mut leaf_seen = vec![false; leaves];
2508        let mut internal_seen = vec![false; internal];
2509        internal_seen[0] = true;
2510        for &c in left.iter().chain(right) {
2511            let seen = if c >= 0 {
2512                leaf_seen.get_mut(c as usize)
2513            } else {
2514                // `!c` is `-c - 1`, without overflow for `i64::MIN`.
2515                internal_seen.get_mut(!c as usize).filter(|_| !c != 0)
2516            };
2517            match seen {
2518                Some(seen) if !*seen => *seen = true,
2519                _ => return false,
2520            }
2521        }
2522        // ... and every internal node is reachable from the root (no cycles).
2523        let mut reached = 1;
2524        let mut stack = vec![0usize];
2525        while let Some(i) = stack.pop() {
2526            for c in [left[i], right[i]] {
2527                if c < 0 {
2528                    reached += 1;
2529                    if reached > internal {
2530                        return false;
2531                    }
2532                    stack.push(!c as usize);
2533                }
2534            }
2535        }
2536        reached == internal
2537    }
2538
2539    /// Checks the dendrogram invariant on an HRG produced by igraph.
2540    fn validated(self) -> Result<Self> {
2541        if self.is_valid_dendrogram() {
2542            Ok(self)
2543        } else {
2544            Err(Error::new(
2545                ErrorKind::Internal,
2546                "igraph produced an invalid HRG dendrogram",
2547            ))
2548        }
2549    }
2550
2551    /// Raw pointer to the underlying C struct, for FFI calls.
2552    pub fn as_ptr(&self) -> *const igraph_hrg_t {
2553        &self.raw
2554    }
2555
2556    /// The number of leaves, i.e. of vertices of the modeled graph.
2557    ///
2558    /// Binds [`igraph_hrg_size`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_size).
2559    pub fn size(&self) -> usize {
2560        unsafe { igraph_hrg_size(&self.raw) as usize }
2561    }
2562
2563    /// Left child of each internal node (non-negative: leaf, `-j - 1`: internal node `j`).
2564    pub fn left(&self) -> &[i64] {
2565        &self.raw.left
2566    }
2567
2568    /// Right child of each internal node (non-negative: leaf, `-j - 1`: internal node `j`).
2569    pub fn right(&self) -> &[i64] {
2570        &self.raw.right
2571    }
2572
2573    /// Connection probability of each internal node.
2574    pub fn prob(&self) -> &[f64] {
2575        &self.raw.prob
2576    }
2577
2578    /// Edge count stored for each internal node. For models fitted by MCMC
2579    /// ([`Graph::hrg_fit`], ...) this is the number of graph edges whose
2580    /// endpoints have this node as lowest common ancestor (they sum to the
2581    /// number of edges of the graph); [`Hrg::create`] stores the number of
2582    /// dendrogram edges below the node instead.
2583    pub fn edges(&self) -> &[i64] {
2584        &self.raw.edges
2585    }
2586
2587    /// Number of leaves in the subtree of each internal node.
2588    pub fn vertices(&self) -> &[i64] {
2589        &self.raw.vertices
2590    }
2591
2592    /// Creates an HRG from its dendrogram given as a directed binary tree
2593    /// and the probabilities of its internal nodes.
2594    ///
2595    /// `tree` must be a simple directed tree with edges pointing away from
2596    /// the root, in which every internal node has exactly two children; it
2597    /// has `n` leaves and `n − 1` internal nodes (at least 3 vertices in total).
2598    /// `prob` has one entry per internal node (`vcount / 2` values), and
2599    /// `prob[v]` is the probability of the internal tree vertex `v`: igraph
2600    /// indexes it by tree vertex id, so the internal vertices must be
2601    /// numbered first (`0..n-1`), as in the example of [`Hrg`] (this is
2602    /// checked). The leaves of the tree become the vertices `0..n` of the
2603    /// model, in increasing id order.
2604    ///
2605    /// Binds [`igraph_hrg_create`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_create).
2606    ///
2607    /// # Errors
2608    /// [`ErrorKind::InvalidValue`] if `tree`
2609    /// is not a valid dendrogram or `prob` has a wrong length.
2610    pub fn create(tree: &Graph, prob: &[f64]) -> Result<Self> {
2611        let n = tree.vcount();
2612        if n < 3 {
2613            return Err(Error::invalid("HRG tree must have at least three vertices"));
2614        }
2615        check_len("the HRG probability vector", prob.len(), n / 2)?;
2616        // igraph (1.0.0 and 1.0.1) indexes `prob` by the id of every tree
2617        // vertex with out-degree 2 without a bounds check (only after checking
2618        // that the tree is directed): make sure these ids are in range.
2619        let degrees = if tree.is_directed() {
2620            tree.degree(.., NeighborMode::Out, crate::constants::Loops::Twice)?
2621        } else {
2622            Vec::new()
2623        };
2624        if let Some(v) = degrees
2625            .iter()
2626            .enumerate()
2627            .find(|&(v, &d)| d == 2 && v >= prob.len())
2628        {
2629            return Err(Error::invalid(format!(
2630                "internal vertex {} of the HRG tree has no probability: internal \
2631                 vertices must have the smallest ids",
2632                v.0
2633            )));
2634        }
2635        let p = Vector::view(prob);
2636        let mut hrg = Self::placeholder(0)?;
2637        igraph_call!(igraph_hrg_create(&mut hrg.raw, tree, p.as_ptr()))?;
2638        hrg.validated()
2639    }
2640
2641    /// Draws a random graph from the model: every pair of vertices is
2642    /// connected independently with the probability of its lowest common
2643    /// ancestor in the dendrogram. The result is undirected and simple.
2644    ///
2645    /// Binds [`igraph_hrg_sample`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_sample).
2646    pub fn sample(&self) -> Result<Graph> {
2647        Graph::init_with(|g| unsafe { igraph_hrg_sample(&self.raw, g) })
2648    }
2649
2650    /// Draws `num_samples` independent random graphs from the model.
2651    ///
2652    /// Binds `igraph_hrg_sample_many` (see the
2653    /// [HRG chapter](https://igraph.org/c/html/latest/igraph-HRG.html) of the
2654    /// C documentation).
2655    ///
2656    /// # Examples
2657    /// ```
2658    /// use igraph::prelude::*;
2659    /// use igraph::community::Hrg;
2660    /// // Two leaves joined by a root with probability 1/2.
2661    /// let tree = Graph::from_edges(&[(0, 1), (0, 2)], 3, true).unwrap();
2662    /// let hrg = Hrg::create(&tree, &[0.5]).unwrap();
2663    /// rng::seed(1).unwrap();
2664    /// let samples = hrg.sample_many(1000).unwrap();
2665    /// let with_edge = samples.iter().filter(|s| s.ecount() == 1).count();
2666    /// assert!((400..600).contains(&with_edge));
2667    /// ```
2668    pub fn sample_many(&self, num_samples: usize) -> Result<Vec<Graph>> {
2669        let mut list = GraphList::new();
2670        igraph_call!(igraph_hrg_sample_many(
2671            &self.raw,
2672            &mut list,
2673            num_samples as igraph_int_t
2674        ))?;
2675        Ok(list.into_vec())
2676    }
2677
2678    /// The dendrogram as a directed tree with the probability of each tree
2679    /// vertex, see [`Graph::from_hrg_dendrogram`].
2680    pub fn dendrogram(&self) -> Result<(Graph, Vec<f64>)> {
2681        Graph::from_hrg_dendrogram(self)
2682    }
2683}
2684
2685impl Clone for Hrg {
2686    fn clone(&self) -> Self {
2687        Self {
2688            raw: igraph_hrg_t {
2689                left: self.raw.left.clone(),
2690                right: self.raw.right.clone(),
2691                prob: self.raw.prob.clone(),
2692                vertices: self.raw.vertices.clone(),
2693                edges: self.raw.edges.clone(),
2694            },
2695        }
2696    }
2697}
2698
2699impl fmt::Debug for Hrg {
2700    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2701        f.debug_struct("Hrg")
2702            .field("size", &self.size())
2703            .field("left", &self.left())
2704            .field("right", &self.right())
2705            .field("prob", &self.prob())
2706            .field("edges", &self.edges())
2707            .field("vertices", &self.vertices())
2708            .finish()
2709    }
2710}
2711
2712impl PartialEq for Hrg {
2713    fn eq(&self, other: &Self) -> bool {
2714        self.left() == other.left()
2715            && self.right() == other.right()
2716            && self.prob() == other.prob()
2717            && self.edges() == other.edges()
2718            && self.vertices() == other.vertices()
2719    }
2720}
2721
2722/// Result of [`Graph::hrg_consensus`].
2723#[derive(Debug, Clone, PartialEq)]
2724pub struct HrgConsensus {
2725    /// Parent of each node of the consensus tree (`-1` for roots): ids
2726    /// `0..n` are the vertices of the graph, larger ids are vertex groups.
2727    pub parents: Vec<i64>,
2728    /// For each internal node of the consensus tree (ids `n..`), how many
2729    /// times its split occurred in the samples.
2730    pub weights: Vec<f64>,
2731    /// The model the sampling started from: a copy of the given start model,
2732    /// or the model fitted to equilibrium when none was given.
2733    pub hrg: Hrg,
2734}
2735
2736/// Result of [`Graph::hrg_predict`].
2737#[derive(Debug, Clone, PartialEq)]
2738pub struct HrgPrediction {
2739    /// The candidate missing edges, most likely first.
2740    pub edges: Vec<(VertexId, VertexId)>,
2741    /// The estimated probability of each candidate edge.
2742    pub prob: Vec<f64>,
2743    /// The model the sampling started from: a copy of the given start model,
2744    /// or the model fitted to equilibrium when none was given.
2745    pub hrg: Hrg,
2746}
2747
2748impl igraph_t {
2749    fn check_hrg_start(&self, hrg: &Hrg) -> Result<()> {
2750        if hrg.size() != self.vcount() {
2751            return Err(Error::invalid(format!(
2752                "the HRG has {} leaves but the graph has {} vertices",
2753                hrg.size(),
2754                self.vcount()
2755            )));
2756        }
2757        Ok(())
2758    }
2759
2760    /// Fits a hierarchical random graph model to the graph by Markov chain
2761    /// Monte Carlo (Clauset, Moore and Newman, 2008).
2762    ///
2763    /// Runs `steps` MCMC steps, or, with `steps = 0`, until a convergence
2764    /// criterion is met (the average log-likelihood over 65536 steps
2765    /// stabilizes), starting from a random dendrogram. The returned model is
2766    /// the most likely dendrogram visited. Edge directions, multi-edges and
2767    /// loops are ignored; the graph needs at least 3 vertices. The chain is
2768    /// random: seed the thread's generator for reproducible models.
2769    ///
2770    /// With `steps > 0`, igraph (1.0.0 and 1.0.1) only records a dendrogram
2771    /// when a step improves on the likelihood of the random initial one; on
2772    /// tiny graphs this may never happen, and the binding then runs the chain
2773    /// to equilibrium instead, so that a valid model is always returned.
2774    ///
2775    /// Binds [`igraph_hrg_fit`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_fit).
2776    ///
2777    /// # Examples
2778    /// ```
2779    /// use igraph::prelude::*;
2780    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2781    ///     .unwrap();
2782    /// rng::seed(42).unwrap();
2783    /// let hrg = g.hrg_fit(1000).unwrap();
2784    /// assert_eq!(hrg.size(), 6);
2785    /// assert!(hrg.prob().iter().all(|p| (0.0..=1.0).contains(p)));
2786    /// ```
2787    pub fn hrg_fit(&self, steps: usize) -> Result<Hrg> {
2788        let steps = igraph_int_t::try_from(steps)
2789            .map_err(|_| Error::invalid("the number of MCMC steps is too large"))?;
2790        let mut hrg = Hrg::placeholder(self.vcount())?;
2791        igraph_call!(igraph_hrg_fit(self, &mut hrg.raw, false, steps))?;
2792        if steps > 0 && !hrg.is_valid_dendrogram() {
2793            // With a fixed number of steps igraph records a dendrogram only
2794            // when a step improves on the likelihood of its random initial
2795            // dendrogram. If none did, `hrg` is still the zero-filled
2796            // placeholder, which is not a tree: run to equilibrium instead,
2797            // which always records its result.
2798            igraph_call!(igraph_hrg_fit(self, &mut hrg.raw, false, 0))?;
2799        }
2800        hrg.validated()
2801    }
2802
2803    /// Continues fitting an HRG to the graph, starting the MCMC from `hrg`
2804    /// (updated in place). See [`Graph::hrg_fit`].
2805    ///
2806    /// `hrg` is replaced by the most likely dendrogram visited, and is left
2807    /// unchanged if no step improves on its likelihood (or on error). With
2808    /// `steps = 0` the chain runs until convergence.
2809    ///
2810    /// Binds [`igraph_hrg_fit`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_fit)
2811    /// with `start = true`.
2812    ///
2813    /// # Errors
2814    /// [`ErrorKind::InvalidValue`] if the size of `hrg` differs from the
2815    /// number of vertices.
2816    ///
2817    /// # Examples
2818    /// ```
2819    /// use igraph::prelude::*;
2820    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2821    ///     .unwrap();
2822    /// rng::seed(42).unwrap();
2823    /// let mut hrg = g.hrg_fit(100).unwrap();
2824    /// g.hrg_refit(&mut hrg, 1000).unwrap();
2825    /// // Still a model of `g`: its internal nodes account for all the edges.
2826    /// assert_eq!(hrg.size(), 6);
2827    /// assert_eq!(hrg.edges().iter().sum::<i64>(), 7);
2828    /// ```
2829    pub fn hrg_refit(&self, hrg: &mut Hrg, steps: usize) -> Result<()> {
2830        self.check_hrg_start(hrg)?;
2831        let steps = igraph_int_t::try_from(steps)
2832            .map_err(|_| Error::invalid("the number of MCMC steps is too large"))?;
2833        // Work on a copy, so that `hrg` stays a valid model (and unchanged)
2834        // if anything goes wrong.
2835        let mut fitted = hrg.clone();
2836        igraph_call!(igraph_hrg_fit(self, &mut fitted.raw, true, steps))?;
2837        *hrg = fitted.validated()?;
2838        Ok(())
2839    }
2840
2841    /// Consensus tree of the HRG models sampled for the graph.
2842    ///
2843    /// Starting from `start` (or from a freshly fitted model when `None`),
2844    /// `num_samples` HRGs are sampled by MCMC and the splits present in the
2845    /// majority of them form the consensus tree (a forest when some splits
2846    /// are not supported by a majority: `-1` marks its roots).
2847    ///
2848    /// Binds [`igraph_hrg_consensus`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_consensus).
2849    ///
2850    /// # Errors
2851    /// [`ErrorKind::InvalidValue`] if `start` does not match the graph or the
2852    /// graph has fewer than 3 vertices.
2853    ///
2854    /// # Examples
2855    /// ```
2856    /// use igraph::prelude::*;
2857    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2858    ///     .unwrap();
2859    /// rng::seed(3).unwrap();
2860    /// let cons = g.hrg_consensus(None, 10).unwrap();
2861    /// // Vertices 0..6 come first, then the internal nodes of the consensus tree.
2862    /// assert_eq!(cons.parents.len(), 6 + cons.weights.len());
2863    /// assert!(cons.parents[..6].iter().all(|&p| p == -1 || p >= 6));
2864    /// ```
2865    pub fn hrg_consensus(&self, start: Option<&Hrg>, num_samples: usize) -> Result<HrgConsensus> {
2866        // igraph stores the number of samples in a C `int` here.
2867        let num_samples = i32::try_from(num_samples)
2868            .map_err(|_| Error::invalid("the number of samples is too large"))?;
2869        let mut hrg = match start {
2870            Some(h) => {
2871                self.check_hrg_start(h)?;
2872                h.clone()
2873            }
2874            None => Hrg::placeholder(self.vcount())?,
2875        };
2876        let mut parents = VectorInt::new();
2877        let mut weights = Vector::new();
2878        igraph_call!(igraph_hrg_consensus(
2879            self,
2880            &mut parents,
2881            &mut weights,
2882            &mut hrg.raw,
2883            start.is_some(),
2884            igraph_int_t::from(num_samples)
2885        ))?;
2886        Ok(HrgConsensus {
2887            parents: parents.into(),
2888            weights: weights.into(),
2889            hrg: hrg.validated()?,
2890        })
2891    }
2892
2893    /// Predicts missing edges with HRG models.
2894    ///
2895    /// Samples `num_samples` HRGs (starting from `start`, or from a freshly
2896    /// fitted model when `None`) and estimates, for every non-adjacent vertex
2897    /// pair, the probability that the edge exists but was not observed.
2898    /// `num_bins` controls the resolution of the probabilities (e.g. 25);
2899    /// note that igraph keeps a histogram of `num_bins + 1` values for every
2900    /// vertex pair, i.e. O(|V|² num_bins) memory. The candidates are the
2901    /// pairs of distinct, non-adjacent vertices, most likely first.
2902    ///
2903    /// Binds [`igraph_hrg_predict`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_predict).
2904    ///
2905    /// # Errors
2906    /// [`ErrorKind::InvalidValue`] if `start` does not match the graph, the
2907    /// graph has fewer than 3 or more than 46341 vertices (igraph counts the
2908    /// candidate pairs in a C `int`), or `num_bins` is zero.
2909    ///
2910    /// # Examples
2911    /// ```
2912    /// use igraph::prelude::*;
2913    /// // A 4-cycle: the two diagonals are the only missing links.
2914    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], 4, false).unwrap();
2915    /// rng::seed(1).unwrap();
2916    /// let pred = g.hrg_predict(None, 10, 10).unwrap();
2917    /// let mut edges = pred.edges.clone();
2918    /// edges.sort();
2919    /// assert_eq!(edges, vec![(0, 2), (1, 3)]);
2920    /// ```
2921    pub fn hrg_predict(
2922        &self,
2923        start: Option<&Hrg>,
2924        num_samples: usize,
2925        num_bins: usize,
2926    ) -> Result<HrgPrediction> {
2927        // igraph allocates `num_bins + 1` bins per vertex pair, counted in a
2928        // C `int`; zero bins would give NaN probabilities.
2929        let num_bins = i32::try_from(num_bins)
2930            .ok()
2931            .filter(|&b| (1..i32::MAX).contains(&b))
2932            .ok_or_else(|| Error::invalid("the number of bins must be in 1..2^31 - 1"))?;
2933        let num_samples = igraph_int_t::try_from(num_samples)
2934            .map_err(|_| Error::invalid("the number of samples is too large"))?;
2935        // igraph reports too small graphs with a generic failure here, and
2936        // computes the number of vertex pairs `n (n - 1) / 2` in a C `int`,
2937        // which overflows (undefined behavior) beyond 46341 vertices.
2938        let n = self.vcount();
2939        if !(3..=46341).contains(&n) {
2940            return Err(Error::invalid(format!(
2941                "HRG link prediction needs 3 to 46341 vertices, the graph has {n}"
2942            )));
2943        }
2944        let mut hrg = match start {
2945            Some(h) => {
2946                self.check_hrg_start(h)?;
2947                h.clone()
2948            }
2949            None => Hrg::placeholder(self.vcount())?,
2950        };
2951        let mut edges = VectorInt::new();
2952        let mut prob = Vector::new();
2953        igraph_call!(igraph_hrg_predict(
2954            self,
2955            &mut edges,
2956            &mut prob,
2957            &mut hrg.raw,
2958            start.is_some(),
2959            num_samples,
2960            igraph_int_t::from(num_bins)
2961        ))?;
2962        let edges = edges
2963            .as_chunks::<2>()
2964            .0
2965            .iter()
2966            .map(|&[a, b]| (a, b))
2967            .collect();
2968        Ok(HrgPrediction {
2969            edges,
2970            prob: prob.into(),
2971            hrg: hrg.validated()?,
2972        })
2973    }
2974
2975    /// Samples a graph from an HRG model (same as [`Hrg::sample`]); listed
2976    /// with the other random graph generators of [`games`](crate::games) in
2977    /// the C API.
2978    ///
2979    /// Binds [`igraph_hrg_game`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_hrg_game).
2980    ///
2981    /// # Examples
2982    /// ```
2983    /// use igraph::prelude::*;
2984    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)], 6, false)
2985    ///     .unwrap();
2986    /// rng::seed(42).unwrap();
2987    /// let hrg = g.hrg_fit(0).unwrap();
2988    /// let sample = Graph::hrg_game(&hrg).unwrap();
2989    /// assert_eq!(sample.vcount(), 6);
2990    /// assert!(!sample.is_directed());
2991    /// ```
2992    pub fn hrg_game(hrg: &Hrg) -> Result<Graph> {
2993        Graph::init_with(|g| unsafe { igraph_hrg_game(g, &hrg.raw) })
2994    }
2995
2996    /// The dendrogram of an HRG as a directed tree, with the probability of
2997    /// each tree vertex.
2998    ///
2999    /// The tree has `2n − 1` vertices: `0..n` are the leaves (the modeled
3000    /// vertices, probability `NaN`) and `n + i` is internal node `i` (with
3001    /// probability `hrg.prob()[i]`); edges point from parents to children.
3002    /// Draw it with a tree layout such as
3003    /// [`Graph::layout_reingold_tilford`].
3004    ///
3005    /// Binds [`igraph_from_hrg_dendrogram`](https://igraph.org/c/html/latest/igraph-HRG.html#igraph_from_hrg_dendrogram).
3006    ///
3007    /// # Examples
3008    /// ```
3009    /// use igraph::prelude::*;
3010    /// use igraph::community::Hrg;
3011    /// let tree = Graph::from_edges(&[(0, 3), (0, 1), (1, 4), (1, 2), (2, 5), (2, 6)], 7, true).unwrap();
3012    /// let hrg = Hrg::create(&tree, &[1.0, 0.5, 0.0]).unwrap();
3013    /// let (dendrogram, prob) = Graph::from_hrg_dendrogram(&hrg).unwrap();
3014    /// assert_eq!((dendrogram.vcount(), dendrogram.ecount()), (7, 6));
3015    /// assert!(dendrogram.is_tree(NeighborMode::Out).unwrap());
3016    /// assert!(prob[..4].iter().all(|p| p.is_nan()));
3017    /// assert_eq!(&prob[4..], hrg.prob());
3018    /// ```
3019    pub fn from_hrg_dendrogram(hrg: &Hrg) -> Result<(Graph, Vec<f64>)> {
3020        let mut prob = Vector::new();
3021        let g =
3022            Graph::init_with(|g| unsafe { igraph_from_hrg_dendrogram(g, &hrg.raw, &mut prob) })?;
3023        Ok((g, prob.into()))
3024    }
3025}