Skip to main content

igraph/
structural.rs

1//! Basic structural properties of graphs (`igraph_structural.h`).
2//!
3//! This module binds the functions declared in igraph's
4//! [`igraph_structural.h`](https://igraph.org/c/html/latest/igraph-Structural.html)
5//! header, as further methods of [`Graph`]. They answer the everyday
6//! questions one asks about a network:
7//!
8//! - *how big and how dense is it?* ([`density`](Graph::density),
9//!   [`mean_degree`](Graph::mean_degree), [`maxdegree`](Graph::maxdegree),
10//!   [`strength`](Graph::strength));
11//! - *is it a simple graph?* (loops, multi-edges and mutual edges);
12//! - *what kind of graph is it?* (tree, forest, acyclic, complete, chordal,
13//!   perfect; cliques and independent sets);
14//! - *how are degrees correlated?* (average nearest neighbor degree,
15//!   `k_nn(k)`, rich-club density sequence);
16//! - *which spanning trees does it have?* (minimum and uniformly random);
17//! - *what does its Laplacian look like?* (dense and sparse).
18//!
19//! # Example
20//!
21//! ```
22//! use igraph::prelude::*;
23//!
24//! // A 5-cycle with a chord, plus a pendant vertex.
25//! let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0), (0, 2), (4, 5)], 6, false)?;
26//! assert_eq!(g.mean_degree(true)?, 14.0 / 6.0);
27//! assert_eq!(g.maxdegree(.., NeighborMode::All, Loops::Twice)?, 3);
28//! assert!((g.density(None, false)? - 7.0 / 15.0).abs() < 1e-12);
29//! assert!(g.is_simple(true)?);
30//! assert_eq!(g.girth()?, Some(3));
31//! assert!(!g.is_tree(NeighborMode::All)?);
32//! assert!(g.is_clique(&[0, 1, 2], false)?);
33//! assert!(g.is_independent_vertex_set(&[1, 3, 5])?);
34//!
35//! // A spanning tree of a connected graph has n - 1 edges.
36//! let mst = g.minimum_spanning_tree(None, MstAlgorithm::Automatic)?;
37//! assert_eq!(mst.len(), 5);
38//!
39//! // Zachary's karate club: 34 members, 78 friendships, and the two leaders
40//! // (vertices 0 and 33) are the best connected members.
41//! let karate = Graph::famous("Zachary")?;
42//! assert_eq!(karate.maxdegree(.., NeighborMode::All, Loops::Twice)?, 17);
43//! let hubs = karate.sort_vertex_ids_by_degree(.., NeighborMode::All, Loops::Twice, Order::Descending, false)?;
44//! assert_eq!(&hubs[..2], &[33, 0]);
45//! # Ok::<(), igraph::Error>(())
46//! ```
47//!
48//! # Provided functionality
49//!
50//! | Topic | Methods of [`Graph`] |
51//! |-------|----------------------|
52//! | Adjacency | [`are_adjacent`](Graph::are_adjacent), [`subcomponent`](Graph::subcomponent) |
53//! | Density and degrees | [`density`](Graph::density), [`mean_degree`](Graph::mean_degree), [`maxdegree`](Graph::maxdegree), [`strength`](Graph::strength), [`sort_vertex_ids_by_degree`](Graph::sort_vertex_ids_by_degree), [`diversity`](Graph::diversity) |
54//! | Loops and multi-edges | [`has_loop`](Graph::has_loop), [`count_loops`](Graph::count_loops), [`is_loop`](Graph::is_loop), [`has_multiple`](Graph::has_multiple), [`is_multiple`](Graph::is_multiple), [`count_multiple`](Graph::count_multiple), [`count_multiple_1`](Graph::count_multiple_1), [`is_simple`](Graph::is_simple) |
55//! | Reciprocity | [`reciprocity`](Graph::reciprocity), [`is_mutual`](Graph::is_mutual), [`has_mutual`](Graph::has_mutual) |
56//! | Graph classes | [`is_tree`](Graph::is_tree), [`tree_root`](Graph::tree_root), [`is_forest`](Graph::is_forest), [`forest_roots`](Graph::forest_roots), [`is_acyclic`](Graph::is_acyclic), [`is_complete`](Graph::is_complete), [`is_perfect`](Graph::is_perfect), [`is_chordal`](Graph::is_chordal), [`is_chordal_with`](Graph::is_chordal_with), [`maximum_cardinality_search`](Graph::maximum_cardinality_search) |
57//! | Vertex sets | [`is_clique`](Graph::is_clique), [`is_independent_vertex_set`](Graph::is_independent_vertex_set) |
58//! | Cycles | [`girth`](Graph::girth), [`girth_with_cycle`](Graph::girth_with_cycle) |
59//! | Spanning trees | [`minimum_spanning_tree`](Graph::minimum_spanning_tree), [`random_spanning_tree`](Graph::random_spanning_tree), [`unfold_tree`](Graph::unfold_tree) |
60//! | Degree correlations | [`avg_nearest_neighbor_degree`](Graph::avg_nearest_neighbor_degree), [`degree_correlation_vector`](Graph::degree_correlation_vector), [`rich_club_sequence`](Graph::rich_club_sequence) |
61//! | Spectral | [`get_laplacian`](Graph::get_laplacian), [`get_laplacian_sparse`](Graph::get_laplacian_sparse), [`get_laplacian_sparsemat`](Graph::get_laplacian_sparsemat), [`LaplacianNormalization`] |
62//!
63//! All the functions of the header are covered.
64//!
65//! # See also
66//!
67//! - Degrees and neighbors: [`Graph::degree`], [`Graph::neighbors`] (core);
68//!   removing loops and multi-edges: [`Graph::simplify`] ([`operators`](crate::operators)).
69//! - Connectivity: [`Graph::connected_components`], [`Graph::is_connected`],
70//!   [`Graph::count_reachable`] ([`components`](crate::components)).
71//! - Cycles and DAGs: [`Graph::find_cycle`], [`Graph::minimum_cycle_basis`],
72//!   [`Graph::is_dag`], [`Graph::topological_sorting`] ([`cycles`](crate::cycles)).
73//! - Cliques, independent sets and colorings: [`Graph::largest_cliques`],
74//!   [`Graph::clique_number`], [`Graph::independence_number`],
75//!   [`Graph::vertex_coloring_greedy`] ([`cliques`](crate::cliques)).
76//! - Degree correlations as a single number: [`Graph::assortativity_degree`],
77//!   [`Graph::joint_degree_matrix`] ([`mixing`](crate::mixing)).
78//! - Matrices of a graph: [`Graph::get_adjacency`], [`Graph::get_adjacency_sparse`]
79//!   ([`conversion`](crate::conversion)); spectra of the Laplacian with
80//!   [`lapack_dsyevr`](crate::linalg::lapack_dsyevr) and
81//!   [`Graph::laplacian_spectral_embedding`] ([`linalg`](crate::linalg)).
82//! - Trees: [`Graph::kary_tree`] ([`constructors`](crate::constructors)),
83//!   [`Graph::tree_game`] ([`games`](crate::games)), [`Graph::to_prufer`]
84//!   ([`conversion`](crate::conversion)), [`Graph::bfs`] ([`visitor`](crate::visitor)).
85//!
86//! # Notes on igraph 1.0.0 and 1.0.1
87//!
88//! A few wrappers work around behaviours of the C library that are present
89//! in both igraph 1.0.0 and 1.0.1 (the source files involved are unchanged
90//! in 1.0.1), so that the Rust API is consistent and memory safe:
91//!
92//! - [`count_multiple_1`](Graph::count_multiple_1) validates the edge id and
93//!   reports an undirected self-loop once, like [`count_multiple`](Graph::count_multiple);
94//! - [`get_laplacian_sparse`](Graph::get_laplacian_sparse) (and
95//!   [`get_laplacian_sparsemat`](Graph::get_laplacian_sparsemat)) ignore edge
96//!   directions with [`NeighborMode::All`], like the dense
97//!   [`get_laplacian`](Graph::get_laplacian);
98//! - [`is_chordal_with`](Graph::is_chordal_with) checks that the given
99//!   vertex orders are permutations (igraph only checks their lengths);
100//! - [`diversity`](Graph::diversity) recomputes the value of degree-one
101//!   vertices, which igraph derives from the weight of edge 0 instead of
102//!   the vertex's own edge.
103//!
104//! One wrapper differs from the C calling convention on purpose:
105//! [`random_spanning_tree`](Graph::random_spanning_tree) spells igraph's
106//! documented "negative vertex id means all components" as `None`, and
107//! rejects negative ids instead of silently spanning all components.
108
109use crate::{
110    constants::*,
111    error::{Error, Result},
112    ffi::*,
113    graph::{EdgeId, Graph, VertexId},
114    igraph_call,
115    linalg::SparseMat,
116    matrix::Matrix,
117    selector::{EdgeSelector, VertexSelector},
118    vector::{Vector, VectorBool, VectorInt},
119};
120use std::{collections::BTreeMap, ptr};
121
122crate::ffi_enum! {
123    /// Normalization of the Laplacian matrix (`igraph_laplacian_normalization_t`),
124    /// used by [`Graph::get_laplacian`], [`Graph::get_laplacian_sparse`] and
125    /// [`Graph::get_laplacian_sparsemat`].
126    ///
127    /// `A` is the (possibly weighted) adjacency matrix and `D` the diagonal
128    /// matrix of degrees (or strengths, if weighted); out-, in- or total
129    /// degrees are used according to the `mode` argument.
130    pub enum LaplacianNormalization: igraph_laplacian_normalization_t {
131        /// Unnormalized Laplacian, `L = D - A`.
132        Unnormalized = igraph_laplacian_normalization_t_IGRAPH_LAPLACIAN_UNNORMALIZED,
133        /// Symmetrically normalized Laplacian, `L = I - D^(-1/2) A D^(-1/2)`.
134        Symmetric = igraph_laplacian_normalization_t_IGRAPH_LAPLACIAN_SYMMETRIC,
135        /// Left-stochastic normalized Laplacian, `L = I - D^-1 A` (rows sum to
136        /// zero when `D` holds the row sums of `A`, e.g. out-degrees).
137        Left = igraph_laplacian_normalization_t_IGRAPH_LAPLACIAN_LEFT,
138        /// Right-stochastic normalized Laplacian, `L = I - A D^-1` (columns sum
139        /// to zero when `D` holds the column sums of `A`, e.g. in-degrees).
140        Right = igraph_laplacian_normalization_t_IGRAPH_LAPLACIAN_RIGHT,
141    }
142}
143
144impl Default for LaplacianNormalization {
145    /// [`LaplacianNormalization::Unnormalized`].
146    fn default() -> Self {
147        Self::Unnormalized
148    }
149}
150
151/// Result of [`Graph::avg_nearest_neighbor_degree`].
152#[derive(Debug, Clone, PartialEq)]
153pub struct NeighborDegree {
154    /// `knn[i]` is the (possibly weighted) average degree of the neighbors of
155    /// the `i`-th selected vertex; NaN for isolated vertices (with weights:
156    /// for vertices of zero strength).
157    pub knn: Vec<f64>,
158    /// The `k_nn(k)` degree correlation function: `knnk[k - 1]` is the mean
159    /// of `knn` over the selected vertices of degree `k` (with weights, the
160    /// mean weighted by their strengths), NaN for degrees that do not occur.
161    /// Note the shift: the first element is for degree one. Its length is the
162    /// maximum degree (according to `mode`) of the whole graph.
163    pub knnk: Vec<f64>,
164}
165
166/// Result of [`Graph::maximum_cardinality_search`].
167#[derive(Debug, Clone, PartialEq)]
168pub struct CardinalitySearch {
169    /// `alpha[v]` is the rank of vertex `v`, in `0..n`; visiting vertices by
170    /// *decreasing* rank always picks the vertex with most visited neighbors.
171    pub alpha: Vec<i64>,
172    /// The inverse permutation of `alpha`: `alpham1[r]` is the vertex of rank
173    /// `r`, i.e. the vertices in *reverse* maximum cardinality search order.
174    pub alpham1: Vec<VertexId>,
175}
176
177/// Result of [`Graph::is_chordal_with`].
178#[derive(Debug, Clone, PartialEq)]
179pub struct Chordality {
180    /// Whether the graph is chordal.
181    pub is_chordal: bool,
182    /// The fill-in (chordal completion): edges whose addition makes the
183    /// graph chordal. Empty for chordal graphs; not necessarily minimal.
184    pub fill_in: Vec<(VertexId, VertexId)>,
185    /// The triangulated graph: a copy of the original graph (same
186    /// directedness) with the fill-in edges appended after the original ones.
187    pub triangulated: Graph,
188}
189
190/// Result of [`Graph::unfold_tree`].
191#[derive(Debug, Clone, PartialEq)]
192pub struct UnfoldedTree {
193    /// The unfolded tree (or forest); it has the directedness of the input.
194    pub tree: Graph,
195    /// `vertex_index[v]` is the vertex of the original graph that vertex `v`
196    /// of `tree` is a copy of. The first `vcount` entries are the identity.
197    pub vertex_index: Vec<VertexId>,
198}
199
200/// Returns a pointer to the optional weight view, or null.
201fn weights_ptr(w: &Option<crate::vector::View<'_, Vector>>) -> *const igraph_vector_t {
202    w.as_ref().map_or(ptr::null(), |v| v.as_ptr())
203}
204
205/// Checks that an optional weight vector has one entry per edge.
206fn check_weights(graph: &Graph, weights: Option<&[f64]>) -> Result<()> {
207    match weights {
208        Some(w) if w.len() != graph.ecount() => Err(Error::invalid(format!(
209            "weight vector length ({}) must match the number of edges ({})",
210            w.len(),
211            graph.ecount()
212        ))),
213        _ => Ok(()),
214    }
215}
216
217/// Whether `p` is a permutation of `0..n`.
218fn is_permutation(p: &[i64], n: usize) -> bool {
219    if p.len() != n {
220        return false;
221    }
222    let mut seen = vec![false; n];
223    p.iter()
224        .all(|&x| (0..n as i64).contains(&x) && !std::mem::replace(&mut seen[x as usize], true))
225}
226
227/// Runs `f` (a C call building an `IGRAPH_ALL`, `IGRAPH_NO_MULTIPLE`
228/// adjacency list) so that igraph's property cache cannot switch off the
229/// removal of duplicate neighbors.
230///
231/// igraph 1.0.1 `igraph_adjlist_init` / `igraph_lazy_adjlist_init` skip the
232/// deduplication when the cache says the graph has no multi-edges. On a
233/// directed graph with a mutual pair `u -> v`, `v -> u` that is wrong in
234/// `IGRAPH_ALL` mode (each of `u`, `v` is listed twice as a neighbor of the
235/// other), and `adjlist_init` then even caches `HAS_MULTI = true`. For
236/// directed graphs the cache is therefore cleared before and after `f`.
237/// Undirected graphs are not affected.
238/// See [`Graph::with_fresh_multi_cache`](crate::graph); this delegates to it
239/// (which also drops the cache when `f` panics).
240pub(crate) fn with_multi_cache_guard<T>(graph: &Graph, f: impl FnOnce() -> T) -> T {
241    graph.with_fresh_multi_cache(f)
242}
243
244impl igraph_t {
245    // ------------------------------------------------------------------
246    // Basic queries
247    // ------------------------------------------------------------------
248
249    /// Decides whether there is an edge from `v1` to `v2`.
250    ///
251    /// In directed graphs the direction matters (`v1 → v2`); in undirected
252    /// graphs the relation is symmetric.
253    ///
254    /// Binds [`igraph_are_adjacent`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_are_adjacent).
255    /// Time complexity: `O(min(log d1, log d2))` where `d1` is the
256    /// (out-)degree of `v1` and `d2` the (in-)degree of `v2`.
257    ///
258    /// # Errors
259    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for
260    /// invalid vertex ids.
261    ///
262    /// # Examples
263    /// ```
264    /// use igraph::prelude::*;
265    /// let g = Graph::from_edges(&[(0, 1)], 3, true)?;
266    /// assert!(g.are_adjacent(0, 1)?);
267    /// assert!(!g.are_adjacent(1, 0)?);
268    /// assert_eq!(g.are_adjacent(0, 9).unwrap_err().kind(), ErrorKind::InvalidVertexId);
269    /// # Ok::<(), igraph::Error>(())
270    /// ```
271    ///
272    /// See also [`Graph::neighbors`] and [`Graph::get_adjacency`] for the
273    /// whole neighborhood or adjacency matrix.
274    pub fn are_adjacent(&self, v1: VertexId, v2: VertexId) -> Result<bool> {
275        let mut res = false;
276        igraph_call!(igraph_are_adjacent(self, v1, v2, &mut res))?;
277        Ok(res)
278    }
279
280    /// The multiplicity of the selected edges: for each edge, the number of
281    /// edges between its two endpoints (itself included).
282    ///
283    /// A simple graph gives all ones. In directed graphs, `(a, b)` and
284    /// `(b, a)` are different edges and don't count towards each other.
285    ///
286    /// Binds [`igraph_count_multiple`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_count_multiple).
287    /// Time complexity: `O(E d)`, `E` the number of edges to check and `d`
288    /// the average degree of their tails.
289    ///
290    /// # Examples
291    /// ```
292    /// use igraph::prelude::*;
293    /// let g = Graph::from_edges(&[(0, 1), (0, 1), (1, 2), (0, 1)], 3, false)?;
294    /// assert_eq!(g.count_multiple(..)?, vec![3, 3, 1, 3]);
295    /// # Ok::<(), igraph::Error>(())
296    /// ```
297    ///
298    /// See also [`is_multiple`](Self::is_multiple), and [`Graph::simplify`]
299    /// to merge parallel edges.
300    pub fn count_multiple<'a>(&self, edges: impl Into<EdgeSelector<'a>>) -> Result<Vec<i64>> {
301        let es = edges.into().to_raw()?;
302        let mut res = VectorInt::new();
303        igraph_call!(igraph_count_multiple(self, &mut res, es.get()))?;
304        Ok(res.into())
305    }
306
307    /// The multiplicity of a single edge, see [`count_multiple`](Self::count_multiple).
308    ///
309    /// The result always agrees with the corresponding entry of
310    /// [`count_multiple`](Self::count_multiple), self-loops of undirected
311    /// graphs included: the C function of igraph 1.0.0 and 1.0.1 counts each
312    /// undirected self-loop twice (it scans the neighbor list of the tail,
313    /// where a loop appears twice), and this wrapper halves that count.
314    ///
315    /// Binds [`igraph_count_multiple_1`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_count_multiple_1).
316    /// Time complexity: `O(d)`, the out-degree of the tail of the edge.
317    ///
318    /// # Errors
319    /// [`ErrorKind::InvalidEdgeId`](crate::ErrorKind::InvalidEdgeId) for an invalid edge id.
320    ///
321    /// # Examples
322    /// ```
323    /// use igraph::prelude::*;
324    /// let g = Graph::from_edges(&[(0, 1), (1, 0), (2, 2), (2, 2)], 3, false)?;
325    /// assert_eq!(g.count_multiple_1(0)?, 2);
326    /// assert_eq!(g.count_multiple_1(2)?, 2); // two self-loops on vertex 2
327    /// assert_eq!(g.count_multiple_1(4).unwrap_err().kind(), ErrorKind::InvalidEdgeId);
328    /// # Ok::<(), igraph::Error>(())
329    /// ```
330    pub fn count_multiple_1(&self, edge: EdgeId) -> Result<i64> {
331        // igraph 1.0.0 and 1.0.1 do not validate the id (they would read
332        // out of bounds).
333        if !(0..self.ecount() as EdgeId).contains(&edge) {
334            return Err(Error::new(
335                crate::ErrorKind::InvalidEdgeId,
336                format!("invalid edge id {edge}"),
337            ));
338        }
339        let mut res = 0;
340        igraph_call!(igraph_count_multiple_1(self, &mut res, edge))?;
341        // igraph 1.0.0 and 1.0.1 count the neighbors of the tail, where an
342        // undirected self-loop appears twice: halve the count so that it
343        // agrees with `igraph_count_multiple` (which reports the number of
344        // loops).
345        let (from, to) = self.edge(edge)?;
346        if from == to && !self.is_directed() {
347            res /= 2;
348        }
349        Ok(res)
350    }
351
352    /// The density of the graph: the ratio of the number of edges to the
353    /// largest possible number of edges.
354    ///
355    /// The maximum number of edges is `n(n-1)/2` for undirected and `n(n-1)`
356    /// for directed graphs; when `loops` is `true`, self-loops are considered
357    /// possible and these become `n(n+1)/2` and `n²`. With `loops = false`
358    /// the result is only correct if the graph has no loops (this is not
359    /// checked).
360    ///
361    /// With `weights`, the total edge weight is used instead of the edge
362    /// count, and the result may exceed 1 (the same happens with
363    /// multigraphs). For the null graph the result is NaN.
364    ///
365    /// Binds [`igraph_density`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_density).
366    /// Time complexity: `O(1)` (`O(|E|)` when weighted).
367    ///
368    /// # Examples
369    /// ```
370    /// use igraph::prelude::*;
371    /// let triangle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false)?;
372    /// assert_eq!(triangle.density(None, false)?, 1.0);
373    /// assert_eq!(triangle.density(None, true)?, 0.5); // 3 of 6 possible edges
374    /// assert_eq!(triangle.density(Some(&[0.5, 0.5, 0.5]), false)?, 0.5);
375    /// # Ok::<(), igraph::Error>(())
376    /// ```
377    ///
378    /// See also [`mean_degree`](Self::mean_degree) and
379    /// [`rich_club_sequence`](Self::rich_club_sequence), which computes the
380    /// density of a sequence of shrinking subgraphs.
381    pub fn density(&self, weights: Option<&[f64]>, loops: bool) -> Result<f64> {
382        check_weights(self, weights)?;
383        let w = weights.map(Vector::view);
384        let mut res = 0.0;
385        igraph_call!(igraph_density(self, weights_ptr(&w), &mut res, loops))?;
386        Ok(res)
387    }
388
389    /// The structural diversity index of the selected vertices.
390    ///
391    /// It is the Shannon entropy of the weights of the incident edges,
392    /// normalized by the logarithm of the degree:
393    /// `D(i) = H(i) / log k(i)` with `H(i) = -Σ_j p_ij log p_ij` and
394    /// `p_ij = w_ij / Σ_l w_il`, `k(i)` being the degree (zero-weight edges
395    /// included). Isolated vertices get NaN, vertices of degree one get 0
396    /// (NaN if the weight of their only edge is zero, as for any vertex whose
397    /// incident weights are all zero). Defined by Eagle, Macy and Claxton
398    /// (Science 328, 2010).
399    ///
400    /// igraph 1.0.0 and 1.0.1 compute the value of degree-one vertices from
401    /// the weight of edge 0 rather than of the vertex's own edge; this
402    /// wrapper recomputes those entries.
403    ///
404    /// The graph must be undirected and without multi-edges; `weights`
405    /// (required, one non-negative value per edge) are mandatory.
406    ///
407    /// Binds [`igraph_diversity`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_diversity).
408    /// Time complexity: `O(|V| + |E|)`.
409    ///
410    /// # Errors
411    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for directed
412    /// graphs, multigraphs, negative weights or a wrong number of weights.
413    ///
414    /// # Examples
415    /// ```
416    /// use igraph::prelude::*;
417    /// // A star with equal weights has maximal diversity at the center.
418    /// let star = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], 4, false)?;
419    /// let d = star.diversity(&[1.0, 1.0, 1.0], ..)?;
420    /// assert!((d[0] - 1.0).abs() < 1e-12);
421    /// assert_eq!(&d[1..], &[0.0, 0.0, 0.0]);
422    /// // A zero weight: the center's entropy is log 2 over log 3 (the edge
423    /// // still counts in its degree), and leaf 1 has no weight at all.
424    /// let d = star.diversity(&[0.0, 1.0, 1.0], ..)?;
425    /// assert!((d[0] - 2f64.ln() / 3f64.ln()).abs() < 1e-12);
426    /// assert!(d[1].is_nan());
427    /// assert_eq!(&d[2..], &[0.0, 0.0]);
428    /// # Ok::<(), igraph::Error>(())
429    /// ```
430    pub fn diversity<'a>(
431        &self,
432        weights: &[f64],
433        vertices: impl Into<VertexSelector<'a>>,
434    ) -> Result<Vec<f64>> {
435        check_weights(self, Some(weights))?;
436        let vs = vertices.into().to_raw()?;
437        let w = Vector::view(weights);
438        let mut res = Vector::new();
439        igraph_call!(igraph_diversity(self, w.as_ptr(), &mut res, vs.get()))?;
440        // igraph 1.0.0 and 1.0.1 decide the value of a degree-one vertex by
441        // looking at the weight of edge 0 instead of the vertex's own edge:
442        // recompute these entries (0 if the edge weight is positive, NaN if
443        // it is zero, as documented).
444        let mut ids = VectorInt::new();
445        igraph_call!(igraph_vs_as_vector(self, vs.get(), &mut ids))?;
446        let degrees = self.degree(&ids, NeighborMode::All, Loops::Twice)?;
447        let mut res: Vec<f64> = res.into();
448        for ((d, &v), _) in res
449            .iter_mut()
450            .zip(ids.iter())
451            .zip(&degrees)
452            .filter(|(_, k)| **k == 1)
453        {
454            if let [e] = self.incident(v, NeighborMode::All, Loops::Twice)?[..] {
455                *d = if weights[e as usize] > 0.0 {
456                    0.0
457                } else {
458                    f64::NAN
459                };
460            }
461        }
462        Ok(res)
463    }
464
465    /// The girth of the graph: the length of its shortest cycle, or `None`
466    /// if the graph has no cycles.
467    ///
468    /// Edge directions are ignored; self-loops and multi-edges are ignored
469    /// as well, i.e. cycles of length 1 or 2 are not considered, so the
470    /// girth is always at least 3. `None` is returned exactly for graphs
471    /// without such cycles (forests, possibly with loops and multi-edges).
472    /// Algorithm by Itai and Rodeh (1977).
473    ///
474    /// Mutual edges `u -> v`, `v -> u` of a directed graph count as a single
475    /// undirected edge. igraph 1.0.1 can get this wrong when its property
476    /// cache already records "no multi-edges" (a girth of 2, or heap
477    /// corruption in the chordality code), so for directed graphs this
478    /// wrapper clears the cache around the C call.
479    ///
480    /// Binds [`igraph_girth`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_girth).
481    /// Time complexity: `O((|V| + |E|)²)` in general, `O(|V| + |E|)` for
482    /// acyclic graphs.
483    ///
484    /// # Examples
485    /// ```
486    /// use igraph::prelude::*;
487    /// // The Petersen graph has girth 5.
488    /// assert_eq!(Graph::famous("Petersen")?.girth()?, Some(5));
489    ///
490    /// let path = Graph::from_edges(&[(0, 1), (1, 2)], 3, false)?;
491    /// assert_eq!(path.girth()?, None);
492    /// // Loops and multi-edges do not form cycles here.
493    /// let multi = Graph::from_edges(&[(0, 0), (0, 1), (0, 1)], 2, false)?;
494    /// assert_eq!(multi.girth()?, None);
495    /// # Ok::<(), igraph::Error>(())
496    /// ```
497    ///
498    /// See also [`Graph::find_cycle`] (any cycle, respecting directions) and
499    /// [`Graph::minimum_cycle_basis`] (a basis of short cycles).
500    pub fn girth(&self) -> Result<Option<usize>> {
501        let mut girth = 0.0;
502        with_multi_cache_guard(self, || {
503            igraph_call!(igraph_girth(self, &mut girth, ptr::null_mut()))
504        })?;
505        Ok(girth.is_finite().then_some(girth as usize))
506    }
507
508    /// Like [`girth`](Self::girth), but also returns the vertex ids of one
509    /// shortest cycle of length at least 3, in cycle order (consecutive
510    /// vertices, and the last and the first, are adjacent); `None` if there
511    /// is no such cycle.
512    ///
513    /// Binds [`igraph_girth`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_girth).
514    ///
515    /// # Examples
516    /// ```
517    /// use igraph::prelude::*;
518    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0), (1, 3)], 4, false)?;
519    /// let (girth, cycle) = g.girth_with_cycle()?.unwrap();
520    /// assert_eq!(girth, 3);
521    /// assert_eq!(cycle.len(), 3);
522    /// assert!(g.is_clique(&cycle, false)?); // a triangle
523    /// # Ok::<(), igraph::Error>(())
524    /// ```
525    pub fn girth_with_cycle(&self) -> Result<Option<(usize, Vec<VertexId>)>> {
526        let mut girth = 0.0;
527        let mut circle = VectorInt::new();
528        with_multi_cache_guard(self, || {
529            igraph_call!(igraph_girth(self, &mut girth, &mut circle))
530        })?;
531        Ok(girth.is_finite().then(|| (girth as usize, circle.into())))
532    }
533
534    /// Whether the graph has at least one self-loop.
535    ///
536    /// The result is cached in the graph, so repeated calls are `O(1)`.
537    ///
538    /// Binds [`igraph_has_loop`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_has_loop).
539    /// Time complexity: `O(|E|)`.
540    ///
541    /// # Examples
542    /// ```
543    /// use igraph::prelude::*;
544    /// let mut g = Graph::from_edges(&[(0, 1), (1, 1)], 2, false)?;
545    /// assert!(g.has_loop()?);
546    /// g.simplify(true, true)?;
547    /// assert!(!g.has_loop()?);
548    /// # Ok::<(), igraph::Error>(())
549    /// ```
550    ///
551    /// See also [`count_loops`](Self::count_loops), [`is_loop`](Self::is_loop)
552    /// and [`Graph::simplify`].
553    pub fn has_loop(&self) -> Result<bool> {
554        let mut res = false;
555        igraph_call!(igraph_has_loop(self, &mut res))?;
556        Ok(res)
557    }
558
559    /// Whether the graph has at least one multi-edge (two or more edges
560    /// with the same endpoints, and the same direction in directed graphs).
561    ///
562    /// The result is cached in the graph, so repeated calls are `O(1)`.
563    ///
564    /// Binds [`igraph_has_multiple`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_has_multiple).
565    /// Time complexity: `O(|E| d)`, `d` the average degree.
566    ///
567    /// # Examples
568    /// ```
569    /// use igraph::prelude::*;
570    /// let mut g = Graph::from_edges(&[(0, 1), (1, 0)], 2, true)?;
571    /// assert!(!g.has_multiple()?); // opposite directions
572    /// g.add_edge(0, 1)?;
573    /// assert!(g.has_multiple()?);
574    /// # Ok::<(), igraph::Error>(())
575    /// ```
576    pub fn has_multiple(&self) -> Result<bool> {
577        let mut res = false;
578        igraph_call!(igraph_has_multiple(self, &mut res))?;
579        Ok(res)
580    }
581
582    /// The number of self-loops in the graph.
583    ///
584    /// Binds [`igraph_count_loops`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_count_loops).
585    /// Time complexity: `O(|E|)`.
586    pub fn count_loops(&self) -> Result<usize> {
587        let mut res = 0;
588        igraph_call!(igraph_count_loops(self, &mut res))?;
589        Ok(res as usize)
590    }
591
592    /// For each selected edge, whether it is a self-loop.
593    ///
594    /// Binds [`igraph_is_loop`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_loop).
595    /// Time complexity: `O(e)`, the number of edges to check.
596    ///
597    /// # Examples
598    /// ```
599    /// use igraph::prelude::*;
600    /// let g = Graph::from_edges(&[(0, 0), (0, 1), (1, 1)], 2, false)?;
601    /// assert_eq!(g.is_loop(..)?, vec![true, false, true]);
602    /// assert_eq!(g.count_loops()?, 2);
603    /// # Ok::<(), igraph::Error>(())
604    /// ```
605    pub fn is_loop<'a>(&self, edges: impl Into<EdgeSelector<'a>>) -> Result<Vec<bool>> {
606        let es = edges.into().to_raw()?;
607        let mut res = VectorBool::new();
608        igraph_call!(igraph_is_loop(self, &mut res, es.get()))?;
609        Ok(res.into())
610    }
611
612    /// For each selected edge, whether it is a multi-edge.
613    ///
614    /// Only the *second and further* occurrences of parallel edges are
615    /// flagged, so that deleting all the flagged edges yields a graph
616    /// without multi-edges (as [`Graph::simplify`] does). In undirected
617    /// graphs `(a, b)` and `(b, a)` are parallel; in directed ones they are not.
618    ///
619    /// Binds [`igraph_is_multiple`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_multiple).
620    /// Time complexity: `O(e d)`.
621    ///
622    /// # Examples
623    /// ```
624    /// use igraph::prelude::*;
625    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 1), (0, 1), (1, 0)], 3, true)?;
626    /// assert_eq!(g.is_multiple(..)?, vec![false, false, false, true, false]);
627    /// # Ok::<(), igraph::Error>(())
628    /// ```
629    pub fn is_multiple<'a>(&self, edges: impl Into<EdgeSelector<'a>>) -> Result<Vec<bool>> {
630        let es = edges.into().to_raw()?;
631        let mut res = VectorBool::new();
632        igraph_call!(igraph_is_multiple(self, &mut res, es.get()))?;
633        Ok(res.into())
634    }
635
636    /// For each selected edge, whether it is *mutual*, i.e. whether the
637    /// reversed edge exists as well.
638    ///
639    /// `loops` decides whether directed self-loops count as mutual. In
640    /// undirected graphs every edge is mutual. Multiplicities are not
641    /// considered: two `(a, b)` edges and one `(b, a)` edge are all mutual.
642    ///
643    /// Binds [`igraph_is_mutual`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_mutual).
644    /// Time complexity: `O(n log d)`, `n` the number of edges to check.
645    ///
646    /// # Examples
647    /// ```
648    /// use igraph::prelude::*;
649    /// let g = Graph::from_edges(&[(0, 1), (1, 0), (1, 2), (2, 2)], 3, true)?;
650    /// assert_eq!(g.is_mutual(.., true)?, vec![true, true, false, true]);
651    /// assert_eq!(g.is_mutual(.., false)?, vec![true, true, false, false]);
652    /// # Ok::<(), igraph::Error>(())
653    /// ```
654    pub fn is_mutual<'a>(
655        &self,
656        edges: impl Into<EdgeSelector<'a>>,
657        loops: bool,
658    ) -> Result<Vec<bool>> {
659        let es = edges.into().to_raw()?;
660        let mut res = VectorBool::new();
661        igraph_call!(igraph_is_mutual(self, &mut res, es.get(), loops))?;
662        Ok(res.into())
663    }
664
665    /// Whether the graph has at least one mutual edge pair (see
666    /// [`is_mutual`](Self::is_mutual)).
667    ///
668    /// Undirected graphs have mutual edges exactly when they have edges. A
669    /// directed graph without mutual edges (and loops) is an *oriented graph*.
670    ///
671    /// Binds [`igraph_has_mutual`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_has_mutual).
672    /// Time complexity: `O(|E| log d)`.
673    ///
674    /// # Examples
675    /// ```
676    /// use igraph::prelude::*;
677    /// let oriented = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, true)?;
678    /// assert!(!oriented.has_mutual(true)?);
679    /// let with_loop = Graph::from_edges(&[(0, 1), (1, 1)], 2, true)?;
680    /// assert!(with_loop.has_mutual(true)?);   // the loop counts as mutual
681    /// assert!(!with_loop.has_mutual(false)?);
682    /// # Ok::<(), igraph::Error>(())
683    /// ```
684    pub fn has_mutual(&self, loops: bool) -> Result<bool> {
685        let mut res = false;
686        igraph_call!(igraph_has_mutual(self, &mut res, loops))?;
687        Ok(res)
688    }
689
690    /// Whether the graph is simple, i.e. it has no self-loops and no
691    /// multi-edges.
692    ///
693    /// With `directed = false`, edge directions are ignored, so a directed
694    /// graph with a mutual edge pair is considered non-simple. Ignored for
695    /// undirected graphs.
696    ///
697    /// Binds [`igraph_is_simple`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_simple).
698    /// Time complexity: `O(|V| + |E|)`.
699    ///
700    /// # Examples
701    /// ```
702    /// use igraph::prelude::*;
703    /// let g = Graph::from_edges(&[(0, 1), (1, 0)], 2, true)?;
704    /// assert!(g.is_simple(true)?);
705    /// assert!(!g.is_simple(false)?);
706    /// # Ok::<(), igraph::Error>(())
707    /// ```
708    pub fn is_simple(&self, directed: bool) -> Result<bool> {
709        let mut res = false;
710        igraph_call!(igraph_is_simple(self, &mut res, directed))?;
711        Ok(res)
712    }
713
714    /// Whether the graph is a tree.
715    ///
716    /// An undirected graph is a tree if it is connected and has no cycles.
717    /// For directed graphs `mode` selects the test: [`NeighborMode::Out`]
718    /// for out-trees (arborescences, edges pointing away from the root),
719    /// [`NeighborMode::In`] for in-trees, [`NeighborMode::All`] to ignore
720    /// directions. The null graph is *not* a tree.
721    ///
722    /// Binds [`igraph_is_tree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_tree).
723    /// Time complexity: `O(|V| + |E|)`.
724    ///
725    /// # Examples
726    /// ```
727    /// use igraph::prelude::*;
728    /// let g = Graph::from_edges(&[(0, 1), (0, 2), (2, 3)], 4, true)?;
729    /// assert!(g.is_tree(NeighborMode::Out)?);
730    /// assert!(!g.is_tree(NeighborMode::In)?);
731    /// assert_eq!(g.tree_root(NeighborMode::Out)?, Some(0));
732    /// // A complete binary tree on 15 vertices.
733    /// assert!(Graph::kary_tree(15, 2, TreeMode::Out)?.is_tree(NeighborMode::Out)?);
734    /// # Ok::<(), igraph::Error>(())
735    /// ```
736    ///
737    /// See also [`Graph::kary_tree`] and [`Graph::tree_game`] to build trees,
738    /// and [`Graph::to_prufer`] to encode them.
739    pub fn is_tree(&self, mode: NeighborMode) -> Result<bool> {
740        let mut res = false;
741        igraph_call!(igraph_is_tree(self, &mut res, ptr::null_mut(), mode.into()))?;
742        Ok(res)
743    }
744
745    /// The root of the graph if it is a tree (see [`is_tree`](Self::is_tree)),
746    /// `None` otherwise.
747    ///
748    /// For out-trees (in-trees) the root is the unique vertex of zero in-degree
749    /// (out-degree); with [`NeighborMode::All`] or in undirected graphs any
750    /// vertex can be the root, and vertex 0 is returned.
751    ///
752    /// Binds [`igraph_is_tree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_tree).
753    pub fn tree_root(&self, mode: NeighborMode) -> Result<Option<VertexId>> {
754        let mut res = false;
755        let mut root = -1;
756        igraph_call!(igraph_is_tree(self, &mut res, &mut root, mode.into()))?;
757        Ok(res.then_some(root))
758    }
759
760    /// Whether the graph is a forest, i.e. every connected component is a
761    /// tree (equivalently, there are no undirected cycles).
762    ///
763    /// `mode` has the same meaning as in [`is_tree`](Self::is_tree). The
764    /// null graph *is* a forest. The result is cached for undirected tests.
765    ///
766    /// Binds [`igraph_is_forest`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_forest).
767    /// Time complexity: `O(|V| + |E|)`.
768    pub fn is_forest(&self, mode: NeighborMode) -> Result<bool> {
769        let mut res = false;
770        igraph_call!(igraph_is_forest(
771            self,
772            &mut res,
773            ptr::null_mut(),
774            mode.into()
775        ))?;
776        Ok(res)
777    }
778
779    /// The roots of the trees if the graph is a forest (see
780    /// [`is_forest`](Self::is_forest)), `None` otherwise.
781    ///
782    /// With [`NeighborMode::All`] or in undirected graphs, one vertex per
783    /// component is returned (the smallest id); for out- (in-) forests the
784    /// roots are the vertices of zero in- (out-) degree.
785    ///
786    /// Binds [`igraph_is_forest`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_forest).
787    ///
788    /// # Examples
789    /// ```
790    /// use igraph::prelude::*;
791    /// let g = Graph::from_edges(&[(0, 1), (2, 3), (2, 4)], 6, false)?;
792    /// assert_eq!(g.forest_roots(NeighborMode::All)?, Some(vec![0, 2, 5]));
793    /// # Ok::<(), igraph::Error>(())
794    /// ```
795    pub fn forest_roots(&self, mode: NeighborMode) -> Result<Option<Vec<VertexId>>> {
796        let mut res = false;
797        let mut roots = VectorInt::new();
798        igraph_call!(igraph_is_forest(self, &mut res, &mut roots, mode.into()))?;
799        Ok(res.then(|| roots.into()))
800    }
801
802    /// Whether the graph has no cycles, taking edge directions into account:
803    /// for directed graphs this is the same as being a DAG.
804    ///
805    /// Self-loops are cycles. The result is cached in the graph.
806    ///
807    /// Binds [`igraph_is_acyclic`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_is_acyclic).
808    /// Time complexity: `O(|V| + |E|)`.
809    ///
810    /// # Examples
811    /// ```
812    /// use igraph::prelude::*;
813    /// let dag = Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], 3, true)?;
814    /// assert!(dag.is_acyclic()?);
815    /// assert!(!dag.is_forest(NeighborMode::All)?); // but it has an undirected cycle
816    /// # Ok::<(), igraph::Error>(())
817    /// ```
818    ///
819    /// See also [`Graph::is_dag`] (false for undirected graphs),
820    /// [`Graph::topological_sorting`] and [`Graph::find_cycle`].
821    pub fn is_acyclic(&self) -> Result<bool> {
822        let mut res = false;
823        igraph_call!(igraph_is_acyclic(self, &mut res))?;
824        Ok(res)
825    }
826
827    /// The maximum degree among the selected vertices (0 if the selection
828    /// is empty).
829    ///
830    /// `mode` selects out-, in- or total degree (ignored for undirected
831    /// graphs); `loops` how self-loops are counted.
832    ///
833    /// Binds [`igraph_maxdegree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_maxdegree).
834    /// Time complexity: `O(v)` when loops are counted, `O(v d)` otherwise.
835    ///
836    /// # Errors
837    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for invalid vertices.
838    ///
839    /// # Examples
840    /// ```
841    /// use igraph::prelude::*;
842    /// let g = Graph::from_edges(&[(0, 1), (0, 2), (3, 0), (1, 1)], 4, true)?;
843    /// assert_eq!(g.maxdegree(.., NeighborMode::Out, Loops::Twice)?, 2);
844    /// assert_eq!(g.maxdegree(.., NeighborMode::All, Loops::Twice)?, 3);
845    /// assert_eq!(g.maxdegree(&[1], NeighborMode::All, Loops::Twice)?, 3); // loop counted twice
846    /// assert_eq!(g.maxdegree(&[1], NeighborMode::All, Loops::None)?, 1);
847    /// # Ok::<(), igraph::Error>(())
848    /// ```
849    ///
850    /// See also [`Graph::degree`] for the individual degrees.
851    pub fn maxdegree<'a>(
852        &self,
853        vertices: impl Into<VertexSelector<'a>>,
854        mode: NeighborMode,
855        loops: Loops,
856    ) -> Result<i64> {
857        let vs = vertices.into().to_raw()?;
858        let mut res = 0;
859        igraph_call!(igraph_maxdegree(
860            self,
861            &mut res,
862            vs.get(),
863            mode.into(),
864            loops.into()
865        ))?;
866        Ok(res)
867    }
868
869    /// The mean degree of the graph.
870    ///
871    /// In directed graphs the mean out-degree equals the mean in-degree, and
872    /// that is what is returned (i.e. `|E| / |V|`); in undirected graphs it is
873    /// `2|E| / |V|`. With `loops = false`, self-loops are ignored. For the
874    /// null graph the result is NaN.
875    ///
876    /// Binds [`igraph_mean_degree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_mean_degree).
877    /// Time complexity: `O(1)` with loops, `O(|E|)` without.
878    ///
879    /// # Examples
880    /// ```
881    /// use igraph::prelude::*;
882    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 2)], 3, false)?;
883    /// assert_eq!(g.mean_degree(true)?, 2.0);
884    /// assert!((g.mean_degree(false)? - 4.0 / 3.0).abs() < 1e-12);
885    /// assert!(Graph::new(0, false).mean_degree(true)?.is_nan());
886    /// # Ok::<(), igraph::Error>(())
887    /// ```
888    pub fn mean_degree(&self, loops: bool) -> Result<f64> {
889        let mut res = 0.0;
890        igraph_call!(igraph_mean_degree(self, &mut res, loops))?;
891        Ok(res)
892    }
893
894    /// The reciprocity of a directed graph.
895    ///
896    /// With [`Reciprocity::Default`] it is the probability that the reverse
897    /// of a randomly chosen edge is also in the graph,
898    /// `1 - Σ_ij |A_ij - A_ji| / (2 Σ_ij A_ij)` (in multigraphs each parallel
899    /// edge needs its own reverse). With [`Reciprocity::Ratio`] it is the
900    /// number of mutually connected (unordered) vertex pairs divided by the
901    /// number of connected pairs.
902    ///
903    /// `ignore_loops` excludes self-loops from the count; otherwise they
904    /// count as mutual. Undirected graphs always give 1; directed graphs
905    /// without edges give NaN.
906    ///
907    /// Binds [`igraph_reciprocity`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_reciprocity).
908    /// Time complexity: `O(|V| + |E|)`.
909    ///
910    /// # Examples
911    /// ```
912    /// use igraph::prelude::*;
913    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 1)], 3, true)?;
914    /// assert!((g.reciprocity(false, Reciprocity::Default)? - 2.0 / 3.0).abs() < 1e-15);
915    /// assert_eq!(g.reciprocity(false, Reciprocity::Ratio)?, 0.5);
916    /// # Ok::<(), igraph::Error>(())
917    /// ```
918    ///
919    /// See also [`is_mutual`](Self::is_mutual) for the individual edges.
920    pub fn reciprocity(&self, ignore_loops: bool, mode: Reciprocity) -> Result<f64> {
921        let mut res = 0.0;
922        igraph_call!(igraph_reciprocity(
923            self,
924            &mut res,
925            ignore_loops,
926            mode.into()
927        ))?;
928        Ok(res)
929    }
930
931    /// The strength (weighted degree) of the selected vertices: the sum of
932    /// the weights of the incident edges.
933    ///
934    /// Without weights this is the ordinary degree (as `f64`). `mode` and
935    /// `loops` are as in [`degree`](Graph::degree).
936    ///
937    /// Binds [`igraph_strength`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_strength).
938    /// Time complexity: `O(|V| + |E|)`.
939    ///
940    /// # Examples
941    /// ```
942    /// use igraph::prelude::*;
943    /// let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], 3, true)?;
944    /// let w = [1.5, 2.0, 4.0];
945    /// assert_eq!(g.strength(.., NeighborMode::Out, Loops::Twice, Some(&w))?, vec![3.5, 4.0, 0.0]);
946    /// assert_eq!(g.strength(.., NeighborMode::In, Loops::Twice, Some(&w))?, vec![0.0, 1.5, 6.0]);
947    /// assert_eq!(g.strength(.., NeighborMode::All, Loops::Twice, None)?, vec![2.0, 2.0, 2.0]);
948    /// # Ok::<(), igraph::Error>(())
949    /// ```
950    pub fn strength<'a>(
951        &self,
952        vertices: impl Into<VertexSelector<'a>>,
953        mode: NeighborMode,
954        loops: Loops,
955        weights: Option<&[f64]>,
956    ) -> Result<Vec<f64>> {
957        check_weights(self, weights)?;
958        let vs = vertices.into().to_raw()?;
959        let w = weights.map(Vector::view);
960        let mut res = Vector::new();
961        igraph_call!(igraph_strength(
962            self,
963            &mut res,
964            vs.get(),
965            mode.into(),
966            loops.into(),
967            weights_ptr(&w)
968        ))?;
969        Ok(res.into())
970    }
971
972    /// The selected vertices sorted by degree.
973    ///
974    /// `mode` and `loops` define the degree, `order` the sort direction.
975    /// With `only_indices = true` the result contains positions within the
976    /// selection instead of vertex ids (this makes no difference when all
977    /// vertices are selected).
978    ///
979    /// Binds [`igraph_sort_vertex_ids_by_degree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_sort_vertex_ids_by_degree).
980    ///
981    /// # Examples
982    /// ```
983    /// use igraph::prelude::*;
984    /// // A star with center 2.
985    /// let g = Graph::from_edges(&[(2, 0), (2, 1), (2, 3), (3, 4)], 5, false)?;
986    /// let hubs = g.sort_vertex_ids_by_degree(.., NeighborMode::All, Loops::Twice, Order::Descending, false)?;
987    /// assert_eq!(hubs[0], 2);
988    /// assert_eq!(hubs[1], 3);
989    /// # Ok::<(), igraph::Error>(())
990    /// ```
991    ///
992    /// Sorting by increasing degree gives a natural vertex order for
993    /// [`rich_club_sequence`](Self::rich_club_sequence).
994    pub fn sort_vertex_ids_by_degree<'a>(
995        &self,
996        vertices: impl Into<VertexSelector<'a>>,
997        mode: NeighborMode,
998        loops: Loops,
999        order: Order,
1000        only_indices: bool,
1001    ) -> Result<Vec<i64>> {
1002        let vs = vertices.into().to_raw()?;
1003        let mut res = VectorInt::new();
1004        igraph_call!(igraph_sort_vertex_ids_by_degree(
1005            self,
1006            &mut res,
1007            vs.get(),
1008            mode.into(),
1009            loops.into(),
1010            order.into(),
1011            only_indices
1012        ))?;
1013        Ok(res.into())
1014    }
1015
1016    /// Whether the graph is *perfect*: the chromatic number of every induced
1017    /// subgraph equals the size of its largest clique.
1018    ///
1019    /// The check is based on the strong perfect graph theorem (Chudnovsky,
1020    /// Robertson, Seymour and Thomas). It may build the complement graph,
1021    /// consuming a lot of memory on large graphs. Worst-case exponential.
1022    ///
1023    /// Binds [`igraph_is_perfect`](https://igraph.org/c/html/latest/igraph-Coloring.html#igraph_is_perfect).
1024    ///
1025    /// # Errors
1026    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the graph
1027    /// is directed or not simple.
1028    ///
1029    /// # Examples
1030    /// ```
1031    /// use igraph::prelude::*;
1032    /// let c5 = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], 5, false)?;
1033    /// assert!(!c5.is_perfect()?); // an odd hole
1034    /// let c6 = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)], 6, false)?;
1035    /// assert!(c6.is_perfect()?); // bipartite
1036    /// assert!(!Graph::famous("Chvatal")?.is_perfect()?);
1037    /// # Ok::<(), igraph::Error>(())
1038    /// ```
1039    ///
1040    /// See also [`is_chordal`](Self::is_chordal) (chordal graphs are perfect)
1041    /// and [`Graph::is_bipartite`] (so are bipartite graphs); for perfect
1042    /// graphs [`Graph::clique_number`] equals the chromatic number.
1043    pub fn is_perfect(&self) -> Result<bool> {
1044        let mut res = false;
1045        igraph_call!(igraph_is_perfect(self, &mut res))?;
1046        Ok(res)
1047    }
1048
1049    // ------------------------------------------------------------------
1050    // Structural properties
1051    // ------------------------------------------------------------------
1052
1053    /// Whether the graph is complete: all pairs of distinct vertices are
1054    /// adjacent (in both directions, for directed graphs).
1055    ///
1056    /// Self-loops and multi-edges are ignored. The null graph and the
1057    /// singleton graph are complete.
1058    ///
1059    /// Binds [`igraph_is_complete`](https://igraph.org/c/html/latest/igraph-Cliques.html#igraph_is_complete).
1060    /// Time complexity: `O(|V| + |E|)`.
1061    ///
1062    /// # Examples
1063    /// ```
1064    /// use igraph::prelude::*;
1065    /// assert!(Graph::full(5, true, false)?.is_complete()?);
1066    /// let one_way = Graph::from_edges(&[(0, 1)], 2, true)?;
1067    /// assert!(!one_way.is_complete()?); // the edge 1 -> 0 is missing
1068    /// # Ok::<(), igraph::Error>(())
1069    /// ```
1070    pub fn is_complete(&self) -> Result<bool> {
1071        let mut res = false;
1072        igraph_call!(igraph_is_complete(self, &mut res))?;
1073        Ok(res)
1074    }
1075
1076    /// Whether the candidate vertices form a clique (all pairs adjacent).
1077    ///
1078    /// With `directed = true` in a directed graph, both directions are
1079    /// required between every pair. Empty and singleton sets are cliques.
1080    ///
1081    /// Binds [`igraph_is_clique`](https://igraph.org/c/html/latest/igraph-Cliques.html#igraph_is_clique).
1082    /// Time complexity: `O(n² log d)`, `n` the size of the candidate set.
1083    ///
1084    /// # Examples
1085    /// ```
1086    /// use igraph::prelude::*;
1087    /// let karate = Graph::famous("Zachary")?;
1088    /// assert!(karate.is_clique(&[0, 1, 2, 3, 7], false)?);
1089    /// // A largest clique found by the cliques module is, of course, a clique.
1090    /// let largest = &karate.largest_cliques()?[0];
1091    /// assert!(karate.is_clique(largest, false)?);
1092    /// # Ok::<(), igraph::Error>(())
1093    /// ```
1094    ///
1095    /// See also [`Graph::maximal_cliques`] and [`Graph::largest_cliques`] to
1096    /// find cliques.
1097    pub fn is_clique<'a>(
1098        &self,
1099        candidate: impl Into<VertexSelector<'a>>,
1100        directed: bool,
1101    ) -> Result<bool> {
1102        let vs = candidate.into().to_raw()?;
1103        let mut res = false;
1104        igraph_call!(igraph_is_clique(self, vs.get(), directed, &mut res))?;
1105        Ok(res)
1106    }
1107
1108    /// Whether the candidate vertices form an independent set (no pair is
1109    /// adjacent). Empty and singleton sets are independent.
1110    ///
1111    /// Self-loops are ignored: a vertex with a loop can still be part of an
1112    /// independent set.
1113    ///
1114    /// Binds [`igraph_is_independent_vertex_set`](https://igraph.org/c/html/latest/igraph-Cliques.html#igraph_is_independent_vertex_set).
1115    /// Time complexity: `O(n² log d)`.
1116    ///
1117    /// # Examples
1118    /// ```
1119    /// use igraph::prelude::*;
1120    /// let c6 = Graph::ring(6, false, false, true)?;
1121    /// assert!(c6.is_independent_vertex_set(&[0, 2, 4])?);
1122    /// assert!(!c6.is_independent_vertex_set(&[0, 1])?);
1123    /// # Ok::<(), igraph::Error>(())
1124    /// ```
1125    ///
1126    /// See also [`Graph::independence_number`] and
1127    /// [`Graph::largest_independent_vertex_sets`].
1128    pub fn is_independent_vertex_set<'a>(
1129        &self,
1130        candidate: impl Into<VertexSelector<'a>>,
1131    ) -> Result<bool> {
1132        let vs = candidate.into().to_raw()?;
1133        let mut res = false;
1134        igraph_call!(igraph_is_independent_vertex_set(self, vs.get(), &mut res))?;
1135        Ok(res)
1136    }
1137
1138    /// The edge ids of a minimum weight spanning tree (a spanning forest if
1139    /// the graph is disconnected).
1140    ///
1141    /// Edge directions are ignored. Without `weights` (or with
1142    /// [`MstAlgorithm::Unweighted`]) an arbitrary spanning tree is returned.
1143    /// The result is deterministic. To get a *maximum* spanning tree, negate
1144    /// the weights. Weights must not be NaN.
1145    ///
1146    /// Binds [`igraph_minimum_spanning_tree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_minimum_spanning_tree).
1147    ///
1148    /// # Examples
1149    /// ```
1150    /// use igraph::prelude::*;
1151    /// // A square with a heavy edge 3-0 and a light diagonal.
1152    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)], 4, false)?;
1153    /// let w = [1.0, 2.0, 1.0, 9.0, 0.5];
1154    /// let mut tree = g.minimum_spanning_tree(Some(&w), MstAlgorithm::Kruskal)?;
1155    /// tree.sort();
1156    /// assert_eq!(tree, vec![0, 2, 4]);
1157    /// // Keep only the tree edges (and all the vertices).
1158    /// let t = g.subgraph_from_edges(&tree, false)?;
1159    /// assert!(t.is_tree(NeighborMode::All)?);
1160    /// # Ok::<(), igraph::Error>(())
1161    /// ```
1162    ///
1163    /// See also [`random_spanning_tree`](Self::random_spanning_tree) and
1164    /// [`Graph::subgraph_from_edges`] to turn the edge ids into a graph.
1165    pub fn minimum_spanning_tree(
1166        &self,
1167        weights: Option<&[f64]>,
1168        method: MstAlgorithm,
1169    ) -> Result<Vec<EdgeId>> {
1170        check_weights(self, weights)?;
1171        let w = weights.map(Vector::view);
1172        let mut res = VectorInt::new();
1173        igraph_call!(igraph_minimum_spanning_tree(
1174            self,
1175            &mut res,
1176            weights_ptr(&w),
1177            method.into()
1178        ))?;
1179        Ok(res.into())
1180    }
1181
1182    /// The edge ids of a spanning tree sampled uniformly at random, via a
1183    /// loop-erased random walk (Wilson's algorithm).
1184    ///
1185    /// Edge directions are ignored; edge multiplicities affect the sampling
1186    /// frequency. With `vertex = Some(v)` only the component of `v` is
1187    /// spanned; with `None`, a random spanning forest of all components is
1188    /// generated. Draws from the calling thread's default random number
1189    /// generator: seed it with [`rng::seed`](crate::rng::seed) for
1190    /// reproducible results (each thread has its own generator).
1191    ///
1192    /// The number of distinct spanning trees of a connected graph is given by
1193    /// Kirchhoff's matrix-tree theorem from the eigenvalues of
1194    /// [`get_laplacian`](Self::get_laplacian): it is the product of the
1195    /// non-zero ones divided by the number of vertices.
1196    ///
1197    /// # Errors
1198    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) if
1199    /// `vertex` is not a valid vertex id.
1200    ///
1201    /// Binds [`igraph_random_spanning_tree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_random_spanning_tree).
1202    ///
1203    /// # Examples
1204    /// ```
1205    /// use igraph::prelude::*;
1206    /// rng::seed(7)?;
1207    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4)], 5, false)?;
1208    /// assert_eq!(g.random_spanning_tree(None)?.len(), 3);    // forest: 2 + 1 edges
1209    /// assert_eq!(g.random_spanning_tree(Some(3))?.len(), 1); // only 3-4
1210    ///
1211    /// // Same seed, same tree.
1212    /// rng::seed(7)?;
1213    /// let a = g.random_spanning_tree(None)?;
1214    /// rng::seed(7)?;
1215    /// assert_eq!(g.random_spanning_tree(None)?, a);
1216    /// # Ok::<(), igraph::Error>(())
1217    /// ```
1218    pub fn random_spanning_tree(&self, vertex: Option<VertexId>) -> Result<Vec<EdgeId>> {
1219        if let Some(v) = vertex.filter(|&v| v < 0) {
1220            return Err(Error::new(
1221                crate::ErrorKind::InvalidVertexId,
1222                format!("invalid vertex id {v}"),
1223            ));
1224        }
1225        let mut res = VectorInt::new();
1226        igraph_call!(igraph_random_spanning_tree(
1227            self,
1228            &mut res,
1229            vertex.unwrap_or(-1)
1230        ))?;
1231        Ok(res.into())
1232    }
1233
1234    /// The vertices reachable from `vertex` (itself included).
1235    ///
1236    /// [`NeighborMode::Out`] follows edges, [`NeighborMode::In`] follows them
1237    /// backwards, [`NeighborMode::All`] ignores directions (which is *not*
1238    /// the union of the other two). In undirected graphs this is the
1239    /// connected component of `vertex`. The vertices are listed in BFS order,
1240    /// starting with `vertex`.
1241    ///
1242    /// Binds [`igraph_subcomponent`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_subcomponent).
1243    /// Time complexity: `O(|V| + |E|)`.
1244    ///
1245    /// # Examples
1246    /// ```
1247    /// use igraph::prelude::*;
1248    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (3, 1)], 4, true)?;
1249    /// assert_eq!(g.subcomponent(1, NeighborMode::Out)?, vec![1, 2]);
1250    /// let mut up = g.subcomponent(1, NeighborMode::In)?;
1251    /// up.sort();
1252    /// assert_eq!(up, vec![0, 1, 3]);
1253    /// # Ok::<(), igraph::Error>(())
1254    /// ```
1255    ///
1256    /// # Errors
1257    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for
1258    /// an invalid `vertex`.
1259    ///
1260    /// See also [`Graph::connected_components`] (all components at once),
1261    /// [`Graph::count_reachable`] and [`Graph::bfs`].
1262    pub fn subcomponent(&self, vertex: VertexId, mode: NeighborMode) -> Result<Vec<VertexId>> {
1263        let mut res = VectorInt::new();
1264        igraph_call!(igraph_subcomponent(self, &mut res, vertex, mode.into()))?;
1265        Ok(res.into())
1266    }
1267
1268    /// Unfolds the graph into a tree (or forest) by a breadth-first search
1269    /// from `roots`, replicating vertices each time they are reached again.
1270    ///
1271    /// Every edge of the graph appears exactly once in the result: non-tree
1272    /// edges lead to fresh copies of their endpoints. `mode` controls which
1273    /// edges the search follows in directed graphs. Give one root per
1274    /// component to unfold every component.
1275    ///
1276    /// Binds [`igraph_unfold_tree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_unfold_tree).
1277    /// Time complexity: `O(|V| + |E|)`.
1278    ///
1279    /// # Examples
1280    /// ```
1281    /// use igraph::prelude::*;
1282    /// // Unfolding a triangle from vertex 0: the closing edge copies a vertex.
1283    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false)?;
1284    /// let unfolded = g.unfold_tree(NeighborMode::All, &[0])?;
1285    /// assert_eq!((unfolded.tree.vcount(), unfolded.tree.ecount()), (4, 3));
1286    /// assert!(unfolded.tree.is_tree(NeighborMode::All)?);
1287    /// assert_eq!(&unfolded.vertex_index[..3], &[0, 1, 2]);
1288    /// # Ok::<(), igraph::Error>(())
1289    /// ```
1290    ///
1291    /// # Errors
1292    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for
1293    /// an invalid root.
1294    pub fn unfold_tree(&self, mode: NeighborMode, roots: &[VertexId]) -> Result<UnfoldedTree> {
1295        let r = VectorInt::view(roots);
1296        let mut index = VectorInt::new();
1297        let tree = Graph::init_with(|t| unsafe {
1298            igraph_unfold_tree(self, t, mode.into(), r.as_ptr(), &mut index)
1299        })?;
1300        Ok(UnfoldedTree {
1301            tree,
1302            vertex_index: index.into(),
1303        })
1304    }
1305
1306    /// Maximum cardinality search (Tarjan and Yannakakis, 1984).
1307    ///
1308    /// Assigns a rank to every vertex so that visiting vertices by
1309    /// decreasing rank always picks the one with the most already visited
1310    /// neighbors. A graph is chordal iff any two higher-ranked neighbors of
1311    /// every vertex are adjacent. Edge directions are ignored.
1312    ///
1313    /// Mutual edges `u -> v`, `v -> u` of a directed graph count as a single
1314    /// undirected edge. igraph 1.0.1 can get this wrong when its property
1315    /// cache already records "no multi-edges" (a girth of 2, or heap
1316    /// corruption in the chordality code), so for directed graphs this
1317    /// wrapper clears the cache around the C call.
1318    ///
1319    /// Binds [`igraph_maximum_cardinality_search`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_maximum_cardinality_search).
1320    /// Time complexity: `O(|V| + |E|)`.
1321    ///
1322    /// # Examples
1323    /// ```
1324    /// use igraph::prelude::*;
1325    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false)?;
1326    /// let mcs = g.maximum_cardinality_search()?;
1327    /// for (v, &rank) in mcs.alpha.iter().enumerate() {
1328    ///     assert_eq!(mcs.alpham1[rank as usize], v as i64);
1329    /// }
1330    /// // The ranks can be reused by the chordality test.
1331    /// let c = g.is_chordal_with(Some(&mcs.alpha), Some(&mcs.alpham1))?;
1332    /// assert!(c.is_chordal);
1333    /// # Ok::<(), igraph::Error>(())
1334    /// ```
1335    pub fn maximum_cardinality_search(&self) -> Result<CardinalitySearch> {
1336        let mut alpha = VectorInt::new();
1337        let mut alpham1 = VectorInt::new();
1338        with_multi_cache_guard(self, || {
1339            igraph_call!(igraph_maximum_cardinality_search(
1340                self,
1341                &mut alpha,
1342                &mut alpham1
1343            ))
1344        })?;
1345        Ok(CardinalitySearch {
1346            alpha: alpha.into(),
1347            alpham1: alpham1.into(),
1348        })
1349    }
1350
1351    /// Whether the graph is chordal: every cycle of four or more vertices
1352    /// has a chord (equivalently, every induced cycle is a triangle).
1353    ///
1354    /// Edge directions are ignored. See [`is_chordal_with`](Self::is_chordal_with)
1355    /// to also get the fill-in.
1356    ///
1357    /// Mutual edges `u -> v`, `v -> u` of a directed graph count as a single
1358    /// undirected edge. igraph 1.0.1 can get this wrong when its property
1359    /// cache already records "no multi-edges" (a girth of 2, or heap
1360    /// corruption in the chordality code), so for directed graphs this
1361    /// wrapper clears the cache around the C call.
1362    ///
1363    /// Binds [`igraph_is_chordal`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_chordal).
1364    /// Time complexity: linear.
1365    ///
1366    /// # Examples
1367    /// ```
1368    /// use igraph::prelude::*;
1369    /// let square = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], 4, false)?;
1370    /// assert!(!square.is_chordal()?);
1371    /// let c = square.is_chordal_with(None, None)?;
1372    /// assert_eq!(c.fill_in.len(), 1); // one diagonal suffices
1373    /// assert!(c.triangulated.is_chordal()?);
1374    /// # Ok::<(), igraph::Error>(())
1375    /// ```
1376    pub fn is_chordal(&self) -> Result<bool> {
1377        let mut res = false;
1378        with_multi_cache_guard(self, || {
1379            igraph_call!(igraph_is_chordal(
1380                self,
1381                ptr::null(),
1382                ptr::null(),
1383                &mut res,
1384                ptr::null_mut(),
1385                ptr::null_mut()
1386            ))
1387        })?;
1388        Ok(res)
1389    }
1390
1391    /// Chordality test that also returns the fill-in (chordal completion)
1392    /// and the triangulated graph.
1393    ///
1394    /// `alpha` and/or `alpham1` may be supplied from a previous
1395    /// [`maximum_cardinality_search`](Self::maximum_cardinality_search) on
1396    /// the same graph (if only one is given, the other is its inverse);
1397    /// with `None`, the search is performed internally. The fill-in is not
1398    /// necessarily minimal.
1399    ///
1400    /// Binds [`igraph_is_chordal`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_is_chordal).
1401    ///
1402    /// igraph 1.0.0 and 1.0.1 only check the lengths of `alpha` and `alpham1`
1403    /// and then index with their values: this wrapper also checks that they
1404    /// are permutations, so that bad input can never cause out-of-bounds reads.
1405    ///
1406    /// # Errors
1407    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `alpha`
1408    /// or `alpham1` is not a permutation of `0..vcount`, or if both are given
1409    /// and they are not inverse of each other.
1410    ///
1411    /// # Examples
1412    /// ```
1413    /// use igraph::prelude::*;
1414    /// // A 5-cycle needs two chords to become chordal.
1415    /// let c5 = Graph::ring(5, false, false, true)?;
1416    /// let c = c5.is_chordal_with(None, None)?;
1417    /// assert!(!c.is_chordal);
1418    /// assert_eq!(c.fill_in.len(), 2);
1419    /// assert_eq!(c.triangulated.ecount(), 7);
1420    /// assert!(c.triangulated.is_chordal()?);
1421    /// # Ok::<(), igraph::Error>(())
1422    /// ```
1423    pub fn is_chordal_with(
1424        &self,
1425        alpha: Option<&[i64]>,
1426        alpham1: Option<&[VertexId]>,
1427    ) -> Result<Chordality> {
1428        // igraph 1.0.0 and 1.0.1 only check the lengths and then index with
1429        // the values: validate them here so that bad input can never read
1430        // out of bounds.
1431        let n = self.vcount();
1432        for (name, perm) in [("alpha", alpha), ("alpham1", alpham1)] {
1433            if let Some(p) = perm
1434                && !is_permutation(p, n)
1435            {
1436                return Err(Error::invalid(format!(
1437                    "{name} must be a permutation of 0..{n}"
1438                )));
1439            }
1440        }
1441        if let (Some(a), Some(am1)) = (alpha, alpham1)
1442            && a.iter()
1443                .enumerate()
1444                .any(|(v, &r)| am1[r as usize] != v as i64)
1445        {
1446            return Err(Error::invalid("alpham1 must be the inverse of alpha"));
1447        }
1448        let a = alpha.map(VectorInt::view);
1449        let am1 = alpham1.map(VectorInt::view);
1450        let ap = a.as_ref().map_or(ptr::null(), |v| v.as_ptr());
1451        let am1p = am1.as_ref().map_or(ptr::null(), |v| v.as_ptr());
1452        let mut chordal = false;
1453        let mut fill_in = VectorInt::new();
1454        let triangulated = with_multi_cache_guard(self, || {
1455            Graph::init_with(|t| unsafe {
1456                igraph_is_chordal(self, ap, am1p, &mut chordal, &mut fill_in, t)
1457            })
1458        })?;
1459        Ok(Chordality {
1460            is_chordal: chordal,
1461            fill_in: fill_in
1462                .as_chunks::<2>()
1463                .0
1464                .iter()
1465                .map(|&[a, b]| (a, b))
1466                .collect(),
1467            triangulated,
1468        })
1469    }
1470
1471    /// Average degree of the neighbors of each selected vertex (`knn`), and
1472    /// its average as a function of the vertex degree (`knnk`).
1473    ///
1474    /// `mode` chooses which neighbors are considered and
1475    /// `neighbor_degree_mode` which of their degrees is averaged (both
1476    /// ignored for undirected graphs). With `weights`, the weighted average
1477    /// `k_nn,u = 1/s_u Σ_v w_uv k_v` of Barrat et al. (PNAS 2004) is computed,
1478    /// `s_u` being the strength of `u`, and `knnk` averages `knn` weighted by
1479    /// the strengths. Isolated vertices (with weights: vertices of zero
1480    /// strength) get NaN, as do degrees that don't occur in `knnk`.
1481    ///
1482    /// Binds [`igraph_avg_nearest_neighbor_degree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_avg_nearest_neighbor_degree).
1483    /// Time complexity: `O(|V| + |E|)`.
1484    ///
1485    /// # Examples
1486    /// ```
1487    /// use igraph::prelude::*;
1488    /// // In a star, the center's neighbors have degree 1, the leaves' neighbor degree 3.
1489    /// let star = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], 4, false)?;
1490    /// let nd = star.avg_nearest_neighbor_degree(.., NeighborMode::All, NeighborMode::All, None)?;
1491    /// assert_eq!(nd.knn, vec![1.0, 3.0, 3.0, 3.0]);
1492    /// assert_eq!(nd.knnk[0], 3.0); // degree-1 vertices
1493    /// assert_eq!(nd.knnk[2], 1.0); // degree-3 vertices
1494    /// # Ok::<(), igraph::Error>(())
1495    /// ```
1496    ///
1497    /// See also [`degree_correlation_vector`](Self::degree_correlation_vector)
1498    /// (the same `k_nn(k)`, averaged over edges, with more mode choices) and
1499    /// [`Graph::assortativity_degree`], which summarizes degree correlations
1500    /// in a single number: a decreasing `k_nn(k)` goes with a negative
1501    /// assortativity.
1502    pub fn avg_nearest_neighbor_degree<'a>(
1503        &self,
1504        vertices: impl Into<VertexSelector<'a>>,
1505        mode: NeighborMode,
1506        neighbor_degree_mode: NeighborMode,
1507        weights: Option<&[f64]>,
1508    ) -> Result<NeighborDegree> {
1509        check_weights(self, weights)?;
1510        let vs = vertices.into().to_raw()?;
1511        let w = weights.map(Vector::view);
1512        let mut knn = Vector::new();
1513        let mut knnk = Vector::new();
1514        igraph_call!(igraph_avg_nearest_neighbor_degree(
1515            self,
1516            vs.get(),
1517            mode.into(),
1518            neighbor_degree_mode.into(),
1519            &mut knn,
1520            &mut knnk,
1521            weights_ptr(&w)
1522        ))?;
1523        Ok(NeighborDegree {
1524            knn: knn.into(),
1525            knnk: knnk.into(),
1526        })
1527    }
1528
1529    /// The degree correlation function `k_nn(k)`: `result[k]` is the mean
1530    /// degree of the targets of edges whose source has degree `k` (NaN for
1531    /// degrees that don't occur). Unlike
1532    /// [`avg_nearest_neighbor_degree`](Self::avg_nearest_neighbor_degree),
1533    /// index 0 is for degree 0.
1534    ///
1535    /// The average is over all directed edges; undirected edges count as two
1536    /// reciprocal directed ones. `from_mode` and `to_mode` define the degree
1537    /// of sources and targets (out-in, out-out, in-in, in-out correlations).
1538    /// With `directed_neighbors = false`, directed edges are also treated as
1539    /// reciprocal pairs. With `weights`, weighted averages are computed.
1540    ///
1541    /// Binds [`igraph_degree_correlation_vector`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_degree_correlation_vector).
1542    /// Time complexity: `O(|V| + |E|)`.
1543    ///
1544    /// # Examples
1545    /// ```
1546    /// use igraph::prelude::*;
1547    /// let star = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], 4, false)?;
1548    /// let knnk = star.degree_correlation_vector(None, NeighborMode::All, NeighborMode::All, true)?;
1549    /// assert_eq!(knnk[1], 3.0);
1550    /// assert_eq!(knnk[3], 1.0);
1551    /// assert!(knnk[0].is_nan() && knnk[2].is_nan());
1552    /// # Ok::<(), igraph::Error>(())
1553    /// ```
1554    ///
1555    /// See also [`Graph::joint_degree_matrix`], from which `k_nn(k)` can be
1556    /// derived, and [`Graph::assortativity_degree`].
1557    pub fn degree_correlation_vector(
1558        &self,
1559        weights: Option<&[f64]>,
1560        from_mode: NeighborMode,
1561        to_mode: NeighborMode,
1562        directed_neighbors: bool,
1563    ) -> Result<Vec<f64>> {
1564        check_weights(self, weights)?;
1565        let w = weights.map(Vector::view);
1566        let mut res = Vector::new();
1567        igraph_call!(igraph_degree_correlation_vector(
1568            self,
1569            weights_ptr(&w),
1570            &mut res,
1571            from_mode.into(),
1572            to_mode.into(),
1573            directed_neighbors
1574        ))?;
1575        Ok(res.into())
1576    }
1577
1578    /// Density of the subgraphs left after removing vertices one by one in
1579    /// the given order (the *rich-club* sequence).
1580    ///
1581    /// `result[i]` is the density of the graph remaining after the first
1582    /// `i` vertices of `vertex_order` have been removed (so `result[0]` is the
1583    /// density of the whole graph). With `normalized = false` the remaining
1584    /// edge count (or total weight) is returned instead. `loops` decides
1585    /// whether self-loops are possible when computing the maximum number of
1586    /// edges (see [`density`](Self::density)); `directed = false` treats a
1587    /// directed graph as undirected. Removing vertices by increasing degree
1588    /// reveals whether high-degree vertices are densely interconnected.
1589    ///
1590    /// `loops` is ignored when `normalized` is `false`. If `loops` is `false`
1591    /// but the graph has self-loops, igraph emits a warning and still divides
1592    /// by the loop-free maximum, so densities may exceed 1.
1593    ///
1594    /// This function is marked *experimental* in igraph 1.0.0 and 1.0.1.
1595    ///
1596    /// Binds [`igraph_rich_club_sequence`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_rich_club_sequence).
1597    /// Time complexity: `O(|V| + |E|)`.
1598    ///
1599    /// # Errors
1600    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
1601    /// `vertex_order` is not a permutation of the vertex ids (wrong length,
1602    /// out-of-range or repeated entries) or the weights have the wrong length.
1603    ///
1604    /// # Examples
1605    /// ```
1606    /// use igraph::prelude::*;
1607    /// // A triangle with a pendant vertex 3 attached to 0; peel off the pendant first.
1608    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (0, 3)], 4, false)?;
1609    /// let seq = g.rich_club_sequence(None, &[3, 0, 1, 2], true, false, false)?;
1610    /// assert!((seq[0] - 4.0 / 6.0).abs() < 1e-12);
1611    /// assert_eq!(seq[1], 1.0); // the triangle is a clique
1612    /// # Ok::<(), igraph::Error>(())
1613    /// ```
1614    ///
1615    /// A natural order removes vertices by increasing degree, see
1616    /// [`sort_vertex_ids_by_degree`](Self::sort_vertex_ids_by_degree).
1617    pub fn rich_club_sequence(
1618        &self,
1619        weights: Option<&[f64]>,
1620        vertex_order: &[VertexId],
1621        normalized: bool,
1622        loops: bool,
1623        directed: bool,
1624    ) -> Result<Vec<f64>> {
1625        check_weights(self, weights)?;
1626        let w = weights.map(Vector::view);
1627        let order = VectorInt::view(vertex_order);
1628        let mut res = Vector::new();
1629        igraph_call!(igraph_rich_club_sequence(
1630            self,
1631            weights_ptr(&w),
1632            &mut res,
1633            order.as_ptr(),
1634            normalized,
1635            loops,
1636            directed
1637        ))?;
1638        Ok(res.into())
1639    }
1640
1641    // ------------------------------------------------------------------
1642    // Spectral properties
1643    // ------------------------------------------------------------------
1644
1645    /// The (dense) Laplacian matrix of the graph.
1646    ///
1647    /// `L_ij = -A_ij` for `i ≠ j` and `L_ii = d_i - A_ii`, where `A` is the
1648    /// (possibly weighted) adjacency matrix and `d_i` the degree (strength).
1649    /// In directed graphs `mode` selects out-degrees (rows sum to zero),
1650    /// in-degrees (columns sum to zero), or ignores directions
1651    /// ([`NeighborMode::All`]). In undirected graphs `A_ii` is twice the
1652    /// number (weight) of self-loops. See [`LaplacianNormalization`] for the
1653    /// normalized variants. Weights must be non-negative and not NaN.
1654    ///
1655    /// For undirected graphs the unnormalized Laplacian is symmetric and
1656    /// positive semi-definite. Without weights (or with positive weights) the
1657    /// multiplicity of its zero eigenvalue is the number of connected
1658    /// components, and, without weights, (matrix-tree theorem) the number of
1659    /// spanning trees of a connected graph is the product of the non-zero
1660    /// eigenvalues divided by `|V|`.
1661    ///
1662    /// Binds [`igraph_get_laplacian`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_get_laplacian).
1663    /// Time complexity: `O(|V|²)`.
1664    ///
1665    /// # Errors
1666    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1667    /// negative or NaN weights, a wrong number of weights, or a normalization
1668    /// that would divide by a zero degree of a non-isolated vertex (e.g.
1669    /// [`LaplacianNormalization::Symmetric`] with [`NeighborMode::Out`] and a
1670    /// vertex with in-edges only).
1671    ///
1672    /// # Examples
1673    /// ```
1674    /// use igraph::{prelude::*, structural::LaplacianNormalization};
1675    /// let path = Graph::from_edges(&[(0, 1), (1, 2)], 3, false)?;
1676    /// let l = path.get_laplacian(NeighborMode::All, LaplacianNormalization::Unnormalized, None)?;
1677    /// assert_eq!(l.to_rows(), vec![
1678    ///     vec![1.0, -1.0, 0.0],
1679    ///     vec![-1.0, 2.0, -1.0],
1680    ///     vec![0.0, -1.0, 1.0],
1681    /// ]);
1682    ///
1683    /// // Matrix-tree theorem: K4 has 4^(4-2) = 16 spanning trees.
1684    /// use igraph::linalg::{lapack_dsyevr, SymmetricRange};
1685    /// let k4 = Graph::full(4, false, false)?;
1686    /// let l = k4.get_laplacian(NeighborMode::All, LaplacianNormalization::Unnormalized, None)?;
1687    /// let eigen = lapack_dsyevr(&l, &SymmetricRange::All, 1e-12)?;
1688    /// let trees: f64 = eigen.values[1..].iter().product::<f64>() / 4.0;
1689    /// assert!((trees - 16.0).abs() < 1e-9);
1690    /// # Ok::<(), igraph::Error>(())
1691    /// ```
1692    ///
1693    /// See also [`get_laplacian_sparse`](Self::get_laplacian_sparse),
1694    /// [`Graph::get_adjacency`], and [`Graph::laplacian_spectral_embedding`].
1695    pub fn get_laplacian(
1696        &self,
1697        mode: NeighborMode,
1698        normalization: LaplacianNormalization,
1699        weights: Option<&[f64]>,
1700    ) -> Result<Matrix> {
1701        check_weights(self, weights)?;
1702        let w = weights.map(Vector::view);
1703        let mut res = Matrix::new();
1704        igraph_call!(igraph_get_laplacian(
1705            self,
1706            &mut res,
1707            mode.into(),
1708            normalization.into(),
1709            weights_ptr(&w)
1710        ))?;
1711        Ok(res)
1712    }
1713
1714    /// The Laplacian matrix in sparse form, as sorted `(row, column, value)`
1715    /// triplets of its non-zero entries.
1716    ///
1717    /// Same definition as [`get_laplacian`](Self::get_laplacian); it takes
1718    /// `O(|V| + |E|)` time and memory, which makes it suitable for large
1719    /// sparse graphs. Duplicate entries produced by igraph (e.g. for
1720    /// multi-edges) are summed, and entries that sum to exactly zero are
1721    /// dropped. Use [`get_laplacian_sparsemat`](Self::get_laplacian_sparsemat)
1722    /// to get a [`SparseMat`] for the sparse linear algebra of
1723    /// [`linalg`](crate::linalg) instead.
1724    ///
1725    /// For directed graphs with [`NeighborMode::All`], the graph is treated
1726    /// as undirected, exactly like the dense variant does (the sparse C
1727    /// implementation of igraph 1.0.0 and 1.0.1 alone would not symmetrize
1728    /// the matrix).
1729    ///
1730    /// Binds [`igraph_get_laplacian_sparse`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_get_laplacian_sparse).
1731    ///
1732    /// # Errors
1733    /// As for [`get_laplacian`](Self::get_laplacian).
1734    ///
1735    /// # Examples
1736    /// ```
1737    /// use igraph::{prelude::*, structural::LaplacianNormalization};
1738    /// let g = Graph::from_edges(&[(0, 1)], 3, false)?;
1739    /// let l = g.get_laplacian_sparse(NeighborMode::All, LaplacianNormalization::Unnormalized, None)?;
1740    /// assert_eq!(l, vec![(0, 0, 1.0), (0, 1, -1.0), (1, 0, -1.0), (1, 1, 1.0)]);
1741    /// # Ok::<(), igraph::Error>(())
1742    /// ```
1743    pub fn get_laplacian_sparse(
1744        &self,
1745        mode: NeighborMode,
1746        normalization: LaplacianNormalization,
1747        weights: Option<&[f64]>,
1748    ) -> Result<Vec<(VertexId, VertexId, f64)>> {
1749        let sparse = self.get_laplacian_sparsemat(mode, normalization, weights)?;
1750        if !sparse.is_triplet() {
1751            // igraph builds the matrix in triplet form; be defensive anyway.
1752            return Err(Error::new(
1753                crate::ErrorKind::Internal,
1754                "expected a sparse Laplacian in triplet form",
1755            ));
1756        }
1757        let el = sparse.getelements()?;
1758        let mut entries: BTreeMap<(VertexId, VertexId), f64> = BTreeMap::new();
1759        for ((&i, &j), &x) in el.i.iter().zip(&el.j).zip(&el.x) {
1760            *entries.entry((i, j)).or_insert(0.0) += x;
1761        }
1762        Ok(entries
1763            .into_iter()
1764            .filter(|&(_, x)| x != 0.0)
1765            .map(|((i, j), x)| (i, j, x))
1766            .collect())
1767    }
1768
1769    /// The Laplacian matrix as a [`SparseMat`] (in triplet form, possibly
1770    /// with duplicate entries, which sparse operations sum up).
1771    ///
1772    /// Same definition, and same handling of directed graphs with
1773    /// [`NeighborMode::All`], as [`get_laplacian_sparse`](Self::get_laplacian_sparse).
1774    /// The result can be fed to the sparse linear algebra of
1775    /// [`linalg`](crate::linalg), e.g. [`SparseMat::mul_vec`] or
1776    /// [`SparseMat::to_dense`].
1777    ///
1778    /// Binds [`igraph_get_laplacian_sparse`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_get_laplacian_sparse).
1779    ///
1780    /// # Errors
1781    /// As for [`get_laplacian`](Self::get_laplacian).
1782    ///
1783    /// # Examples
1784    /// ```
1785    /// use igraph::{prelude::*, structural::LaplacianNormalization};
1786    /// let path = Graph::ring(4, false, false, false)?;
1787    /// let l = path.get_laplacian_sparsemat(NeighborMode::All, LaplacianNormalization::Unnormalized, None)?;
1788    /// // The Laplacian annihilates constant vectors...
1789    /// assert_eq!(l.mul_vec(&[1.0; 4])?, vec![0.0; 4]);
1790    /// // ...and x^T L x is the sum of (x_i - x_j)^2 over the edges.
1791    /// let x = [0.0, 1.0, 3.0, 6.0];
1792    /// let lx = l.mul_vec(&x)?;
1793    /// let quad: f64 = x.iter().zip(&lx).map(|(a, b)| a * b).sum();
1794    /// assert_eq!(quad, 1.0 + 4.0 + 9.0);
1795    /// # Ok::<(), igraph::Error>(())
1796    /// ```
1797    pub fn get_laplacian_sparsemat(
1798        &self,
1799        mode: NeighborMode,
1800        normalization: LaplacianNormalization,
1801        weights: Option<&[f64]>,
1802    ) -> Result<SparseMat> {
1803        check_weights(self, weights)?;
1804        // igraph 1.0.0 and 1.0.1's sparse variant does not symmetrize the
1805        // off-diagonal entries of directed graphs with `IGRAPH_ALL` (unlike
1806        // the dense one): ignore directions explicitly, keeping edge ids
1807        // (and weights).
1808        let undirected;
1809        let graph = if self.is_directed() && mode == NeighborMode::All {
1810            undirected = Graph::from_edges(&self.edge_list(), self.vcount(), false)?;
1811            &undirected
1812        } else {
1813            self
1814        };
1815        let w = weights.map(Vector::view);
1816        let n = self.vcount();
1817        let mut sparse = SparseMat::new(n, n)?;
1818        igraph_call!(igraph_get_laplacian_sparse(
1819            graph,
1820            &mut sparse,
1821            mode.into(),
1822            normalization.into(),
1823            weights_ptr(&w)
1824        ))?;
1825        Ok(sparse)
1826    }
1827}