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}