igraph/centrality.rs
1//! Centrality measures, graph centralization and local scan statistics
2//! (`igraph_centrality.h`, `igraph_scan.h`).
3//!
4//! A *centrality* assigns every vertex (or edge) a score that tells how
5//! "important" it is in the network. Different notions of importance lead to
6//! different measures: being close to everybody else ([closeness](igraph_t::closeness),
7//! [harmonic centrality](igraph_t::harmonic_centrality)), lying on many shortest
8//! paths ([betweenness](igraph_t::betweenness)), being connected to other
9//! important vertices ([eigenvector centrality](igraph_t::eigenvector_centrality),
10//! [PageRank](igraph_t::pagerank), [hub and authority scores](igraph_t::hub_and_authority_scores)),
11//! or bridging structural holes ([Burt's constraint](igraph_t::constraint)).
12//!
13//! A *centralization* index condenses vertex-level scores into a single number
14//! describing how much the whole graph is dominated by a single vertex; it is
15//! usually normalized by its value on the most centralized graph of the same
16//! size (typically a star), the *theoretical maximum*.
17//!
18//! *Local scan statistics* count edges (or sum edge weights) within the
19//! neighborhoods of vertices, and are used for anomaly detection in (time
20//! series of) graphs.
21//!
22//! All the functions are methods of [`Graph`], except for the graph-free
23//! [`centralization`] and the `*_tmax` free functions that compute theoretical
24//! maxima from a number of vertices only.
25//!
26//! # Example
27//!
28//! ```
29//! use igraph::prelude::*;
30//! use igraph::centrality::PageRankOptions;
31//!
32//! // A star with 5 leaves: the center is the most central in every sense.
33//! let star = Graph::star(6, StarMode::Undirected, 0).unwrap();
34//!
35//! let btw = star.betweenness(None, .., true, false).unwrap();
36//! assert_eq!(btw, vec![10.0, 0.0, 0.0, 0.0, 0.0, 0.0]); // C(5, 2) = 10 pairs of leaves
37//!
38//! let clo = star.closeness(.., NeighborMode::All, None, true).unwrap();
39//! assert_eq!(clo[0], 1.0);
40//!
41//! let pr = star.pagerank(None, .., &PageRankOptions::default()).unwrap();
42//! assert!((pr.scores.iter().sum::<f64>() - 1.0).abs() < 1e-12);
43//! assert!(pr.scores[0] > pr.scores[1]);
44//!
45//! // The star is the most centralized graph: normalized centralization is 1.
46//! let c = star.centralization_degree(NeighborMode::All, Loops::None, true).unwrap();
47//! assert!((c.centralization - 1.0).abs() < 1e-12);
48//!
49//! // In Zachary's karate club the instructor (0) and the administrator (33)
50//! // are the two most "between" members.
51//! let karate = Graph::famous("Zachary").unwrap();
52//! let btw = karate.betweenness(None, .., false, false).unwrap();
53//! let mut order: Vec<usize> = (0..34).collect();
54//! order.sort_by(|&a, &b| btw[b].total_cmp(&btw[a]));
55//! assert_eq!(&order[..2], &[0, 33]);
56//! ```
57//!
58//! # Provided functionality
59//!
60//! | Measure | Methods | C functions |
61//! |---|---|---|
62//! | Closeness | [`closeness`](igraph_t::closeness), [`closeness_cutoff`](igraph_t::closeness_cutoff), [`closeness_reachability`](igraph_t::closeness_reachability) | `igraph_closeness`, `igraph_closeness_cutoff` |
63//! | Harmonic centrality | [`harmonic_centrality`](igraph_t::harmonic_centrality), [`harmonic_centrality_cutoff`](igraph_t::harmonic_centrality_cutoff) | `igraph_harmonic_centrality[_cutoff]` |
64//! | Vertex betweenness | [`betweenness`](igraph_t::betweenness), [`betweenness_cutoff`](igraph_t::betweenness_cutoff), [`betweenness_subset`](igraph_t::betweenness_subset) | `igraph_betweenness[_cutoff/_subset]` |
65//! | Edge betweenness | [`edge_betweenness`](igraph_t::edge_betweenness), [`edge_betweenness_cutoff`](igraph_t::edge_betweenness_cutoff), [`edge_betweenness_subset`](igraph_t::edge_betweenness_subset) | `igraph_edge_betweenness[_cutoff/_subset]` |
66//! | PageRank | [`pagerank`](igraph_t::pagerank), [`personalized_pagerank`](igraph_t::personalized_pagerank), [`personalized_pagerank_vs`](igraph_t::personalized_pagerank_vs) | `igraph_pagerank`, `igraph_personalized_pagerank[_vs]` |
67//! | Spectral | [`eigenvector_centrality`](igraph_t::eigenvector_centrality), [`hub_and_authority_scores`](igraph_t::hub_and_authority_scores) | `igraph_eigenvector_centrality`, `igraph_hub_and_authority_scores` |
68//! | Structural holes | [`constraint`](igraph_t::constraint) | `igraph_constraint` |
69//! | Edge convergence | [`convergence_degree`](igraph_t::convergence_degree) | `igraph_convergence_degree` |
70//! | Centralization | [`centralization`], [`centralization_degree`](igraph_t::centralization_degree), [`centralization_betweenness`](igraph_t::centralization_betweenness), [`centralization_closeness`](igraph_t::centralization_closeness), [`centralization_eigenvector_centrality`](igraph_t::centralization_eigenvector_centrality) | `igraph_centralization*` |
71//! | Theoretical maxima | [`centralization_degree_tmax`], [`centralization_betweenness_tmax`], [`centralization_closeness_tmax`], [`centralization_eigenvector_centrality_tmax`] and the homonymous methods of [`Graph`] | `igraph_centralization_*_tmax` |
72//! | Local scan statistics | [`local_scan_0`](igraph_t::local_scan_0), [`local_scan_1_ecount`](igraph_t::local_scan_1_ecount), [`local_scan_k_ecount`](igraph_t::local_scan_k_ecount), their `_them` variants, [`local_scan_subset_ecount`](igraph_t::local_scan_subset_ecount), [`local_scan_neighborhood_ecount`](igraph_t::local_scan_neighborhood_ecount) | `igraph_local_scan_*` |
73//!
74//! # Conventions
75//!
76//! - Edge weights are passed as `Option<&[f64]>`, one weight per edge, `None`
77//! meaning an unweighted computation. The shortest-path based measures
78//! (closeness, harmonic centrality, betweenness) and PageRank reject NaN
79//! weights; betweenness also requires them to be strictly positive.
80//! - Cutoffs are `Option<f64>`: `None` means no limit on the path lengths
81//! (the exact measure is computed); `Some(c)` only considers paths of length
82//! at most `c` (a negative `c` also means "no limit", as in C).
83//! - Vertex and edge sets are anything convertible into a
84//! [`VertexSelector`]/[`EdgeSelector`], e.g. `..` (all), a single id, a
85//! slice or a range of ids. Results follow the order of the selector.
86//! - ARPACK-based computations (eigenvector centrality, hub and authority
87//! scores, [`PageRankAlgo::Arpack`]) always run with igraph's default ARPACK
88//! options, which are adequate for virtually every graph.
89//! - igraph reports non-fatal problems (e.g. eigenvector centrality of a
90//! disconnected graph) as *warnings*, not errors: collect them with
91//! [`take_warnings`](crate::error::take_warnings).
92//!
93//! # See also
94//!
95//! - Shortest-path quantities that closeness and betweenness are built on:
96//! [`distances`](igraph_t::distances),
97//! [`eccentricity`](igraph_t::eccentricity), [`radius`](igraph_t::radius)
98//! and [`average_path_length`](igraph_t::average_path_length) in
99//! [`paths`](crate::paths).
100//! - Degree-like measures: [`degree`](igraph_t::degree),
101//! [`strength`](igraph_t::strength) and [`maxdegree`](igraph_t::maxdegree);
102//! k-core decomposition with [`coreness`](igraph_t::coreness).
103//! - Community detection by removing high-betweenness edges:
104//! [`community_edge_betweenness`](igraph_t::community_edge_betweenness).
105//! - The whole spectrum of the adjacency matrix, of which eigenvector
106//! centrality is the leading eigenvector:
107//! [`eigen_adjacency`](igraph_t::eigen_adjacency).
108//! - Local clustering, the other classic ego-network measure next to Burt's
109//! constraint: [`transitivity_local_undirected`](igraph_t::transitivity_local_undirected)
110//! and [`count_adjacent_triangles`](igraph_t::count_adjacent_triangles).
111
112use crate::{
113 constants::{Loops, NeighborMode},
114 error::{Error, Result},
115 ffi::*,
116 graph::{Graph, VertexId},
117 igraph_call,
118 list::VectorIntList,
119 selector::{EdgeSelector, VertexSelector},
120 vector::{Vector, VectorInt},
121};
122
123crate::ffi_enum! {
124 /// The algorithm used to compute PageRank (`igraph_pagerank_algo_t`).
125 pub enum PageRankAlgo: igraph_pagerank_algo_t {
126 /// Phrase PageRank as an eigenvalue problem and solve it with ARPACK
127 /// (the default before igraph 0.7). The returned
128 /// [`value`](EigenScores::value) is the eigenvalue, which should be 1:
129 /// checking it detects convergence failures.
130 Arpack = igraph_pagerank_algo_t_IGRAPH_PAGERANK_ALGO_ARPACK,
131 /// Solve a linear system with the PRPACK library
132 /// (<https://github.com/dgleich/prpack>). Recommended, and the default.
133 Prpack = igraph_pagerank_algo_t_IGRAPH_PAGERANK_ALGO_PRPACK,
134 }
135}
136
137impl Default for PageRankAlgo {
138 /// [`PageRankAlgo::Prpack`], igraph's recommended implementation.
139 fn default() -> Self {
140 Self::Prpack
141 }
142}
143
144/// Tuning parameters of the PageRank family of functions
145/// ([`pagerank`](igraph_t::pagerank), [`personalized_pagerank`](igraph_t::personalized_pagerank),
146/// [`personalized_pagerank_vs`](igraph_t::personalized_pagerank_vs)).
147///
148/// The [`Default`] is the classic setting: damping `0.85`, directed paths,
149/// [`PageRankAlgo::Prpack`].
150///
151/// ```
152/// use igraph::centrality::{PageRankAlgo, PageRankOptions};
153///
154/// let opts = PageRankOptions::default().with_damping(0.5).with_algo(PageRankAlgo::Arpack);
155/// assert_eq!((opts.damping, opts.directed, opts.algo), (0.5, true, PageRankAlgo::Arpack));
156/// ```
157#[derive(Debug, Clone, Copy, PartialEq)]
158pub struct PageRankOptions {
159 /// The damping factor (`d` in the original paper): the probability that the
160 /// random walker follows an edge instead of restarting. Must be in `[0, 1]`.
161 pub damping: f64,
162 /// Whether to follow edge directions in directed graphs (ignored for
163 /// undirected graphs).
164 pub directed: bool,
165 /// The implementation to use.
166 pub algo: PageRankAlgo,
167}
168
169impl Default for PageRankOptions {
170 fn default() -> Self {
171 Self {
172 damping: 0.85,
173 directed: true,
174 algo: PageRankAlgo::Prpack,
175 }
176 }
177}
178
179impl PageRankOptions {
180 /// Sets the [`damping`](Self::damping) factor.
181 pub fn with_damping(mut self, damping: f64) -> Self {
182 self.damping = damping;
183 self
184 }
185
186 /// Sets whether edge directions are followed ([`directed`](Self::directed)).
187 pub fn with_directed(mut self, directed: bool) -> Self {
188 self.directed = directed;
189 self
190 }
191
192 /// Sets the implementation ([`algo`](Self::algo)).
193 pub fn with_algo(mut self, algo: PageRankAlgo) -> Self {
194 self.algo = algo;
195 self
196 }
197}
198
199/// Closeness scores together with reachability information, returned by
200/// [`closeness_reachability`](igraph_t::closeness_reachability).
201#[derive(Debug, Clone, PartialEq)]
202pub struct Closeness {
203 /// The closeness centrality of each requested vertex (NaN for vertices
204 /// that reach no other vertex).
205 pub scores: Vec<f64>,
206 /// For each requested vertex, the number of vertices reachable from it
207 /// (within the cutoff, if any), not counting the vertex itself.
208 pub reachable_count: Vec<i64>,
209 /// Whether every vertex of the graph was reachable from each requested
210 /// vertex. `false` proves that the graph is disconnected; `true` proves it
211 /// is connected if the graph is undirected, or if it is directed and all
212 /// vertices were requested.
213 pub all_reachable: bool,
214}
215
216/// Vertex scores that are an eigenvector, with the corresponding eigenvalue.
217///
218/// Returned by [`pagerank`](igraph_t::pagerank) and its personalized
219/// variants, and by [`eigenvector_centrality`](igraph_t::eigenvector_centrality).
220#[derive(Debug, Clone, PartialEq)]
221pub struct EigenScores {
222 /// The score of each vertex (for PageRank: of each requested vertex).
223 pub scores: Vec<f64>,
224 /// The eigenvalue. For PageRank it is always `1.0` with PRPACK and should
225 /// be very close to one with ARPACK. For eigenvector centrality it is the
226 /// leading eigenvalue of the adjacency matrix (zero for acyclic graphs,
227 /// where the measure is not meaningful).
228 pub value: f64,
229}
230
231/// Kleinberg's hub and authority scores, see
232/// [`hub_and_authority_scores`](igraph_t::hub_and_authority_scores).
233#[derive(Debug, Clone, PartialEq)]
234pub struct HubAuthority {
235 /// Hub score of each vertex, scaled so that the maximum is 1.
236 pub hubs: Vec<f64>,
237 /// Authority score of each vertex, scaled so that the maximum is 1.
238 pub authorities: Vec<f64>,
239 /// The leading eigenvalue of `A Aᵀ` (equivalently, of `Aᵀ A`).
240 pub value: f64,
241}
242
243/// Convergence degrees of the edges, see
244/// [`convergence_degree`](igraph_t::convergence_degree).
245#[derive(Debug, Clone, PartialEq)]
246pub struct ConvergenceDegree {
247 /// The convergence degree of each edge, in `(-1, 1)`.
248 pub result: Vec<f64>,
249 /// The size of the input set of each edge.
250 pub ins: Vec<f64>,
251 /// The size of the output set of each edge.
252 pub outs: Vec<f64>,
253}
254
255/// Vertex-level scores together with the graph-level centralization index.
256///
257/// Returned by [`centralization_degree`](igraph_t::centralization_degree),
258/// [`centralization_betweenness`](igraph_t::centralization_betweenness) and
259/// [`centralization_closeness`](igraph_t::centralization_closeness).
260#[derive(Debug, Clone, PartialEq)]
261pub struct Centralization {
262 /// The vertex-level centrality scores of all vertices.
263 pub scores: Vec<f64>,
264 /// The graph-level centralization index (normalized if requested).
265 pub centralization: f64,
266 /// The centralization of the most centralized graph with the same number
267 /// of vertices (and directedness).
268 pub theoretical_max: f64,
269}
270
271/// Eigenvector centralities together with the graph-level centralization
272/// index, see [`centralization_eigenvector_centrality`](igraph_t::centralization_eigenvector_centrality).
273#[derive(Debug, Clone, PartialEq)]
274pub struct EigenvectorCentralization {
275 /// The eigenvector centrality of all vertices, scaled so that the maximum is 1.
276 pub scores: Vec<f64>,
277 /// The leading eigenvalue.
278 pub value: f64,
279 /// The graph-level centralization index (normalized if requested).
280 pub centralization: f64,
281 /// The centralization of the most centralized graph with the same number
282 /// of vertices (and directedness).
283 pub theoretical_max: f64,
284}
285
286/// Validates an optional per-edge weight vector and builds a view over it.
287fn weights_view<'w>(
288 graph: &Graph,
289 weights: Option<&'w [f64]>,
290) -> Result<Option<crate::vector::View<'w, Vector>>> {
291 if let Some(w) = weights
292 && w.len() != graph.ecount()
293 {
294 return Err(Error::invalid(format!(
295 "the weight vector has length {}, but the graph has {} edges",
296 w.len(),
297 graph.ecount()
298 )));
299 }
300 Ok(weights.map(Vector::view))
301}
302
303/// Largest sum of absolute weights passed unchanged to the eigenvector-based
304/// (ARPACK / PRPACK) routines: squares of such sums stay finite.
305const MAX_ABS_WEIGHT_SUM: f64 = 1e150;
306
307/// Validates weights for the eigenvector-based centralities and, if they are
308/// so large that the iterations could overflow, rescales them.
309///
310/// igraph (1.0.0 and 1.0.1) does not reject infinite weights (nor NaN ones,
311/// except for PageRank with ARPACK), and huge finite weights overflow the
312/// matrix-vector products: ARPACK then aborts the whole process (an f2c
313/// `STOP`), PRPACK silently returns NaN scores. All these measures are
314/// invariant under scaling the weights (only the eigenvalue scales, by the
315/// returned factor for the adjacency matrix), so weights whose absolute sum
316/// exceeds [`MAX_ABS_WEIGHT_SUM`] are divided by the smallest power of two
317/// that brings the sum below it. A power of two keeps the division exact
318/// and, unlike normalizing the largest weight to `1`, does not flush small
319/// weights to (sub)normal zero: e.g. PageRank divides by the out-strength,
320/// and a vertex whose only out-weight became `1e-320` would get infinite,
321/// then `NaN`, transition probabilities.
322/// Returns the rescaled weights (if any) and the scale factor.
323fn spectral_weights(weights: Option<&[f64]>) -> Result<(Option<Vec<f64>>, f64)> {
324 let Some(w) = weights else {
325 return Ok((None, 1.0));
326 };
327 if let Some(&x) = w.iter().find(|x| !x.is_finite()) {
328 return Err(Error::invalid(format!(
329 "the weights must be finite, found {x}"
330 )));
331 }
332 let sum: f64 = w.iter().map(|x| x.abs()).sum();
333 if sum <= MAX_ABS_WEIGHT_SUM {
334 return Ok((None, 1.0));
335 }
336 // `sum` may have overflowed: compute log2(sum) as log2(max) + log2(sum / max).
337 let max = w.iter().fold(0.0_f64, |m, x| m.max(x.abs()));
338 let rel: f64 = w.iter().map(|x| x.abs() / max).sum();
339 let excess = max.log2() + rel.log2() - MAX_ABS_WEIGHT_SUM.log2();
340 // One extra halving absorbs the rounding of the logarithms. The exponent
341 // is at most about 1024 + 64 - 498, so the factor is finite.
342 let scale = 2f64.powi(excess.ceil() as i32 + 1);
343 Ok((Some(w.iter().map(|x| x / scale).collect()), scale))
344}
345
346/// Validates the PageRank damping factor: igraph's own range check lets NaN
347/// through (and ARPACK then aborts the process).
348fn check_damping(damping: f64) -> Result<()> {
349 if !(0.0..=1.0).contains(&damping) {
350 return Err(Error::invalid(format!(
351 "the PageRank damping factor must be in [0, 1], got {damping}"
352 )));
353 }
354 Ok(())
355}
356
357fn ptr_of(view: &Option<crate::vector::View<'_, Vector>>) -> *const igraph_vector_t {
358 view.as_ref().map_or(std::ptr::null(), |v| v.as_ptr())
359}
360
361fn raw_cutoff(cutoff: Option<f64>) -> f64 {
362 cutoff.unwrap_or(-1.0)
363}
364
365fn raw_nodes(nodes: usize) -> Result<igraph_int_t> {
366 igraph_int_t::try_from(nodes).map_err(|_| Error::invalid("too many vertices"))
367}
368
369/// Checks that `them` is compatible with `us` for the `_them` scan statistics.
370fn check_them(us: &Graph, them: &Graph) -> Result<()> {
371 if us.vcount() != them.vcount() {
372 return Err(Error::invalid(
373 "the two graphs must have the same number of vertices",
374 ));
375 }
376 if us.is_directed() != them.is_directed() {
377 return Err(Error::invalid(
378 "the two graphs must have the same directedness",
379 ));
380 }
381 Ok(())
382}
383
384/// Computes the graph-level centralization index from vertex-level scores
385/// (`igraph_centralization`).
386///
387/// The (unnormalized) centralization is `C = Σ_v (max_u c_u − c_v)`, the sum of
388/// the deviations from the largest score. If `normalized` is true,
389/// `C / theoretical_max` is returned instead, where `theoretical_max` is the
390/// centralization of the most centralized structure with the same number of
391/// vertices (usually a star, see e.g. [`centralization_degree_tmax`]); it is
392/// ignored otherwise. An empty `scores` slice gives NaN. Time complexity:
393/// O(n), the number of scores.
394///
395/// Binds [`igraph_centralization`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization).
396///
397/// # Examples
398///
399/// ```
400/// use igraph::centrality::centralization;
401///
402/// assert_eq!(centralization(&[3.0, 1.0, 1.0, 1.0], 0.0, false), 6.0);
403/// assert_eq!(centralization(&[3.0, 1.0, 1.0, 1.0], 6.0, true), 1.0);
404/// ```
405pub fn centralization(scores: &[f64], theoretical_max: f64, normalized: bool) -> f64 {
406 let view = Vector::view(scores);
407 unsafe { igraph_centralization(view.as_ptr(), theoretical_max, normalized) }
408}
409
410/// Theoretical maximum of degree centralization for a graph with `nodes`
411/// vertices (`igraph_centralization_degree_tmax` with a null graph).
412///
413/// The graph is considered directed unless `mode` is [`NeighborMode::All`].
414/// The most centralized structure is the star (the in- or out-star for
415/// directed graphs). `loops` tells whether self-loops count (and how) in the
416/// degree, since they change the maximum. For `nodes == 0` the result is NaN.
417/// See [`igraph_t::centralization_degree_tmax`] to read size and directedness
418/// from a graph. Time complexity: O(1).
419///
420/// Binds [`igraph_centralization_degree_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_degree_tmax).
421///
422/// # Examples
423///
424/// ```
425/// use igraph::{centrality::centralization_degree_tmax, prelude::*};
426///
427/// // Undirected star on 5 vertices: (n - 1)(n - 2) = 12.
428/// assert_eq!(centralization_degree_tmax(5, NeighborMode::All, Loops::None).unwrap(), 12.0);
429/// ```
430pub fn centralization_degree_tmax(nodes: usize, mode: NeighborMode, loops: Loops) -> Result<f64> {
431 let mut res = 0.0;
432 igraph_call!(igraph_centralization_degree_tmax(
433 std::ptr::null(),
434 raw_nodes(nodes)?,
435 mode.into(),
436 loops.into(),
437 &mut res
438 ))?;
439 Ok(res)
440}
441
442/// Theoretical maximum of betweenness centralization for a graph with `nodes`
443/// vertices (`igraph_centralization_betweenness_tmax` with a null graph).
444///
445/// `directed` tells whether directed paths are used. The most centralized
446/// structure is the star. See [`igraph_t::centralization_betweenness_tmax`]
447/// to read size and directedness from a graph. Time complexity: O(1).
448///
449/// Binds [`igraph_centralization_betweenness_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_betweenness_tmax).
450///
451/// # Examples
452///
453/// ```
454/// use igraph::centrality::centralization_betweenness_tmax;
455///
456/// // Undirected: (n - 1)^2 (n - 2) / 2.
457/// assert_eq!(centralization_betweenness_tmax(5, false).unwrap(), 24.0);
458/// ```
459pub fn centralization_betweenness_tmax(nodes: usize, directed: bool) -> Result<f64> {
460 let mut res = 0.0;
461 igraph_call!(igraph_centralization_betweenness_tmax(
462 std::ptr::null(),
463 raw_nodes(nodes)?,
464 directed,
465 &mut res
466 ))?;
467 Ok(res)
468}
469
470/// Theoretical maximum of closeness centralization for a graph with `nodes`
471/// vertices (`igraph_centralization_closeness_tmax` with a null graph).
472///
473/// The graph is considered directed unless `mode` is [`NeighborMode::All`].
474/// The most centralized structure is the star. The maximum refers to
475/// *normalized* closeness scores, as used by
476/// [`centralization_closeness`](igraph_t::centralization_closeness). Time
477/// complexity: O(1).
478///
479/// Binds [`igraph_centralization_closeness_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_closeness_tmax).
480///
481/// # Examples
482///
483/// ```
484/// use igraph::{centrality::centralization_closeness_tmax, prelude::*};
485///
486/// // Undirected: (n - 1)(n - 2) / (2n - 3).
487/// let t = centralization_closeness_tmax(5, NeighborMode::All).unwrap();
488/// assert!((t - 12.0 / 7.0).abs() < 1e-12);
489/// ```
490pub fn centralization_closeness_tmax(nodes: usize, mode: NeighborMode) -> Result<f64> {
491 let mut res = 0.0;
492 igraph_call!(igraph_centralization_closeness_tmax(
493 std::ptr::null(),
494 raw_nodes(nodes)?,
495 mode.into(),
496 &mut res
497 ))?;
498 Ok(res)
499}
500
501/// Theoretical maximum of eigenvector centralization for a graph with
502/// `nodes` vertices (`igraph_centralization_eigenvector_centrality_tmax`
503/// with a null graph).
504///
505/// The graph is considered directed unless `mode` is [`NeighborMode::All`].
506/// The most centralized undirected structure is a graph with a single edge;
507/// the directed one is the in-star (for [`NeighborMode::Out`]) or the out-star
508/// (for [`NeighborMode::In`]). Scores are assumed to be scaled so that the
509/// maximum is 1. Time complexity: O(1).
510///
511/// Binds [`igraph_centralization_eigenvector_centrality_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_eigenvector_centrality_tmax).
512///
513/// # Examples
514///
515/// ```
516/// use igraph::{centrality::centralization_eigenvector_centrality_tmax, prelude::*};
517///
518/// // Undirected: n - 2 (one edge, all other scores zero).
519/// assert_eq!(centralization_eigenvector_centrality_tmax(10, NeighborMode::All).unwrap(), 8.0);
520/// ```
521pub fn centralization_eigenvector_centrality_tmax(nodes: usize, mode: NeighborMode) -> Result<f64> {
522 let mut res = 0.0;
523 igraph_call!(igraph_centralization_eigenvector_centrality_tmax(
524 std::ptr::null(),
525 raw_nodes(nodes)?,
526 mode.into(),
527 &mut res
528 ))?;
529 Ok(res)
530}
531
532impl igraph_t {
533 // ------------------------------------------------------------------
534 // Closeness and harmonic centrality
535 // ------------------------------------------------------------------
536
537 /// Closeness centrality of the selected vertices (`igraph_closeness`).
538 ///
539 /// The closeness of a vertex is the inverse of the mean distance to (or
540 /// from) all other vertices; it measures how easily other vertices are
541 /// reached from it. `mode` selects the paths in directed graphs:
542 /// [`NeighborMode::Out`] uses distances *from* the vertex,
543 /// [`NeighborMode::In`] distances *to* it and [`NeighborMode::All`] ignores
544 /// directions. With `weights`, path lengths are the sums of edge weights.
545 ///
546 /// If `normalized` is true the result is the inverse of the *mean* distance,
547 /// otherwise the inverse of the *sum* of distances.
548 ///
549 /// Closeness is meaningful for connected graphs only: in disconnected
550 /// graphs igraph only considers *reachable* vertices (in undirected graphs
551 /// this is closeness computed per component). Isolated vertices get NaN.
552 /// Use [`closeness_reachability`](Self::closeness_reachability) to detect
553 /// disconnectedness, or consider [`harmonic_centrality`](Self::harmonic_centrality).
554 ///
555 /// Time complexity: O(n|E|) unweighted, O(n|E|log|V| + |V|) weighted, for
556 /// `n` requested vertices.
557 ///
558 /// See also [`distances`](Self::distances) for the underlying distance
559 /// matrix and [`eccentricity`](Self::eccentricity) for the *largest*
560 /// (instead of the mean) distance from a vertex.
561 ///
562 /// Binds [`igraph_closeness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_closeness).
563 ///
564 /// # Errors
565 ///
566 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for an
567 /// invalid vertex, [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
568 /// for weights of the wrong length or containing NaN.
569 ///
570 /// # Examples
571 ///
572 /// ```
573 /// use igraph::prelude::*;
574 ///
575 /// // Path 0 - 1 - 2: the middle vertex is at distance 1 from both ends.
576 /// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
577 /// assert_eq!(g.closeness(.., NeighborMode::All, None, false).unwrap(), vec![1.0 / 3.0, 0.5, 1.0 / 3.0]);
578 /// assert_eq!(g.closeness(1, NeighborMode::All, None, true).unwrap(), vec![1.0]);
579 /// ```
580 pub fn closeness<'a>(
581 &self,
582 vids: impl Into<VertexSelector<'a>>,
583 mode: NeighborMode,
584 weights: Option<&[f64]>,
585 normalized: bool,
586 ) -> Result<Vec<f64>> {
587 let vs = vids.into().to_raw()?;
588 let w = weights_view(self, weights)?;
589 let mut res = Vector::new();
590 igraph_call!(igraph_closeness(
591 self,
592 &mut res,
593 std::ptr::null_mut(),
594 std::ptr::null_mut(),
595 vs.get(),
596 mode.into(),
597 ptr_of(&w),
598 normalized
599 ))?;
600 Ok(res.into())
601 }
602
603 /// Range-limited closeness centrality (`igraph_closeness_cutoff`).
604 ///
605 /// Like [`closeness`](Self::closeness), but only shortest paths of length
606 /// at most `cutoff` are considered (vertices farther away count as
607 /// unreachable). `None` (or a negative cutoff) computes the exact closeness.
608 /// Smaller cutoffs make the computation faster.
609 ///
610 /// Binds [`igraph_closeness_cutoff`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_closeness_cutoff).
611 ///
612 /// # Errors
613 ///
614 /// As for [`closeness`](Self::closeness).
615 ///
616 /// # Examples
617 ///
618 /// ```
619 /// use igraph::prelude::*;
620 ///
621 /// // Path 0 - 1 - 2 - 3: within distance 1, vertex 0 only reaches vertex 1.
622 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
623 /// let c = g.closeness_cutoff(0, NeighborMode::All, None, true, Some(1.0)).unwrap();
624 /// assert_eq!(c, vec![1.0]);
625 /// ```
626 pub fn closeness_cutoff<'a>(
627 &self,
628 vids: impl Into<VertexSelector<'a>>,
629 mode: NeighborMode,
630 weights: Option<&[f64]>,
631 normalized: bool,
632 cutoff: Option<f64>,
633 ) -> Result<Vec<f64>> {
634 Ok(self
635 .closeness_reachability(vids, mode, weights, normalized, cutoff)?
636 .scores)
637 }
638
639 /// Closeness centrality with reachability information
640 /// (`igraph_closeness_cutoff` with all outputs).
641 ///
642 /// Besides the (possibly range-limited, see
643 /// [`closeness_cutoff`](Self::closeness_cutoff)) closeness scores, it
644 /// returns the number of vertices reachable from each requested vertex and
645 /// whether all vertices were reachable, see [`Closeness`]. These make it
646 /// possible to compute the generalizations of closeness to disconnected
647 /// graphs that rescale scores by the size of the reachable set.
648 ///
649 /// Binds [`igraph_closeness_cutoff`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_closeness_cutoff).
650 ///
651 /// # Errors
652 ///
653 /// As for [`closeness`](Self::closeness).
654 ///
655 /// # Examples
656 ///
657 /// ```
658 /// use igraph::prelude::*;
659 ///
660 /// // Two disjoint edges: closeness is computed within each component.
661 /// let g = Graph::from_edges(&[(0, 1), (2, 3)], 4, false).unwrap();
662 /// let c = g.closeness_reachability(.., NeighborMode::All, None, true, None).unwrap();
663 /// assert_eq!(c.scores, vec![1.0; 4]);
664 /// assert_eq!(c.reachable_count, vec![1; 4]);
665 /// assert!(!c.all_reachable);
666 /// ```
667 pub fn closeness_reachability<'a>(
668 &self,
669 vids: impl Into<VertexSelector<'a>>,
670 mode: NeighborMode,
671 weights: Option<&[f64]>,
672 normalized: bool,
673 cutoff: Option<f64>,
674 ) -> Result<Closeness> {
675 let vs = vids.into().to_raw()?;
676 let w = weights_view(self, weights)?;
677 let mut res = Vector::new();
678 let mut reach = VectorInt::new();
679 let mut all = false;
680 igraph_call!(igraph_closeness_cutoff(
681 self,
682 &mut res,
683 &mut reach,
684 &mut all,
685 vs.get(),
686 mode.into(),
687 ptr_of(&w),
688 normalized,
689 raw_cutoff(cutoff)
690 ))?;
691 Ok(Closeness {
692 scores: res.into(),
693 reachable_count: reach.into(),
694 all_reachable: all,
695 })
696 }
697
698 /// Harmonic centrality of the selected vertices (`igraph_harmonic_centrality`).
699 ///
700 /// The harmonic centrality of a vertex is the sum (or, if `normalized`,
701 /// the mean over the other `|V| - 1` vertices) of the inverse distances to
702 /// all other vertices; unreachable vertices contribute zero, which makes
703 /// this measure well-behaved on disconnected graphs, unlike closeness.
704 /// `mode` and `weights` are as in [`closeness`](Self::closeness).
705 ///
706 /// References: M. Marchiori and V. Latora, *Harmony in the small-world*,
707 /// Physica A 285 (2000); S. Vigna and P. Boldi, *Axioms for Centrality*,
708 /// Internet Mathematics 10 (2014).
709 ///
710 /// Time complexity: O(n|E|) unweighted, O(n|E|log|V| + |V|) weighted.
711 ///
712 /// See also [`global_efficiency`](Self::global_efficiency): the mean of
713 /// the normalized harmonic centralities of all vertices is the global
714 /// efficiency of the graph.
715 ///
716 /// Binds [`igraph_harmonic_centrality`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_harmonic_centrality).
717 ///
718 /// # Errors
719 ///
720 /// As for [`closeness`](Self::closeness).
721 ///
722 /// # Examples
723 ///
724 /// ```
725 /// use igraph::prelude::*;
726 ///
727 /// // Path 0 - 1 - 2: vertex 0 has 1/1 + 1/2 = 1.5.
728 /// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
729 /// assert_eq!(g.harmonic_centrality(.., NeighborMode::All, None, false).unwrap(), vec![1.5, 2.0, 1.5]);
730 /// ```
731 pub fn harmonic_centrality<'a>(
732 &self,
733 vids: impl Into<VertexSelector<'a>>,
734 mode: NeighborMode,
735 weights: Option<&[f64]>,
736 normalized: bool,
737 ) -> Result<Vec<f64>> {
738 let vs = vids.into().to_raw()?;
739 let w = weights_view(self, weights)?;
740 let mut res = Vector::new();
741 igraph_call!(igraph_harmonic_centrality(
742 self,
743 &mut res,
744 vs.get(),
745 mode.into(),
746 ptr_of(&w),
747 normalized
748 ))?;
749 Ok(res.into())
750 }
751
752 /// Range-limited harmonic centrality (`igraph_harmonic_centrality_cutoff`).
753 ///
754 /// Like [`harmonic_centrality`](Self::harmonic_centrality), but vertices
755 /// farther than `cutoff` contribute zero. `None` (or a negative value)
756 /// computes the exact harmonic centrality. Note that normalization still
757 /// divides by `|V| - 1`.
758 ///
759 /// Binds [`igraph_harmonic_centrality_cutoff`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_harmonic_centrality_cutoff).
760 ///
761 /// # Errors
762 ///
763 /// As for [`closeness`](Self::closeness).
764 ///
765 /// # Examples
766 ///
767 /// ```
768 /// use igraph::prelude::*;
769 ///
770 /// // With cutoff 1 the harmonic centrality is just degree / (n - 1).
771 /// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
772 /// let h = g.harmonic_centrality_cutoff(.., NeighborMode::All, None, true, Some(1.0)).unwrap();
773 /// assert_eq!(h, vec![0.5, 1.0, 0.5]);
774 /// ```
775 pub fn harmonic_centrality_cutoff<'a>(
776 &self,
777 vids: impl Into<VertexSelector<'a>>,
778 mode: NeighborMode,
779 weights: Option<&[f64]>,
780 normalized: bool,
781 cutoff: Option<f64>,
782 ) -> Result<Vec<f64>> {
783 let vs = vids.into().to_raw()?;
784 let w = weights_view(self, weights)?;
785 let mut res = Vector::new();
786 igraph_call!(igraph_harmonic_centrality_cutoff(
787 self,
788 &mut res,
789 vs.get(),
790 mode.into(),
791 ptr_of(&w),
792 normalized,
793 raw_cutoff(cutoff)
794 ))?;
795 Ok(res.into())
796 }
797
798 // ------------------------------------------------------------------
799 // Betweenness
800 // ------------------------------------------------------------------
801
802 /// Betweenness centrality of the selected vertices (`igraph_betweenness`).
803 ///
804 /// The betweenness of a vertex `v` is the number of shortest paths passing
805 /// through it; when two vertices are joined by several shortest paths,
806 /// only the fraction of them passing through `v` is counted (Brandes'
807 /// algorithm). With `weights`, weighted shortest paths are used.
808 /// `directed` tells whether to follow edge directions (ignored for
809 /// undirected graphs). If `normalized`, scores are divided by the number of
810 /// vertex pairs: `n(n-1)` ordered pairs when directed paths are used,
811 /// `n(n-1)/2` unordered pairs otherwise (note: *not* the `(n-1)(n-2)`
812 /// convention of some textbooks).
813 ///
814 /// `vids` only selects which scores are returned: internally the
815 /// betweenness of all vertices is computed. Time complexity: O(|V||E|).
816 ///
817 /// Reference: U. Brandes, *A faster algorithm for betweenness centrality*,
818 /// J. Math. Sociol. 25(2), 163–177 (2001).
819 ///
820 /// See also [`get_all_shortest_paths`](Self::get_all_shortest_paths) to
821 /// list the shortest paths that are being counted.
822 ///
823 /// Binds [`igraph_betweenness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_betweenness).
824 ///
825 /// # Errors
826 ///
827 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for an
828 /// invalid vertex, [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
829 /// for invalid weights.
830 ///
831 /// # Examples
832 ///
833 /// ```
834 /// use igraph::prelude::*;
835 ///
836 /// // Path 0 - 1 - 2 - 3: the inner vertices each lie on 2 shortest paths.
837 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
838 /// assert_eq!(g.betweenness(None, .., false, false).unwrap(), vec![0.0, 2.0, 2.0, 0.0]);
839 /// ```
840 pub fn betweenness<'a>(
841 &self,
842 weights: Option<&[f64]>,
843 vids: impl Into<VertexSelector<'a>>,
844 directed: bool,
845 normalized: bool,
846 ) -> Result<Vec<f64>> {
847 let vs = vids.into().to_raw()?;
848 let w = weights_view(self, weights)?;
849 let mut res = Vector::new();
850 igraph_call!(igraph_betweenness(
851 self,
852 ptr_of(&w),
853 &mut res,
854 vs.get(),
855 directed,
856 normalized
857 ))?;
858 Ok(res.into())
859 }
860
861 /// Range-limited betweenness centrality (`igraph_betweenness_cutoff`).
862 ///
863 /// Like [`betweenness`](Self::betweenness), but only shortest paths of
864 /// length at most `cutoff` are counted. `None` (or a negative value)
865 /// computes the exact betweenness.
866 ///
867 /// Binds [`igraph_betweenness_cutoff`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_betweenness_cutoff).
868 ///
869 /// # Errors
870 ///
871 /// As for [`betweenness`](Self::betweenness).
872 ///
873 /// # Examples
874 ///
875 /// ```
876 /// use igraph::prelude::*;
877 ///
878 /// // With cutoff 2 only the paths 0-1-2 and 1-2-3 pass through inner vertices.
879 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
880 /// assert_eq!(g.betweenness_cutoff(None, .., false, false, Some(2.0)).unwrap(), vec![0.0, 1.0, 1.0, 0.0]);
881 /// ```
882 pub fn betweenness_cutoff<'a>(
883 &self,
884 weights: Option<&[f64]>,
885 vids: impl Into<VertexSelector<'a>>,
886 directed: bool,
887 normalized: bool,
888 cutoff: Option<f64>,
889 ) -> Result<Vec<f64>> {
890 let vs = vids.into().to_raw()?;
891 let w = weights_view(self, weights)?;
892 let mut res = Vector::new();
893 igraph_call!(igraph_betweenness_cutoff(
894 self,
895 ptr_of(&w),
896 &mut res,
897 vs.get(),
898 directed,
899 normalized,
900 raw_cutoff(cutoff)
901 ))?;
902 Ok(res.into())
903 }
904
905 /// Betweenness restricted to paths between a set of sources and a set of
906 /// targets (`igraph_betweenness_subset`).
907 ///
908 /// Only the shortest paths starting in `sources` and ending in `targets`
909 /// are counted. Scores are returned for `vids`. In undirected graphs each
910 /// source-target pair contributes with weight 1/2, so that with
911 /// `sources == targets` (where every pair is met from both ends) the
912 /// result agrees with [`betweenness`](Self::betweenness); in particular
913 /// selecting all vertices as sources and targets gives the ordinary
914 /// betweenness. Normalization is not
915 /// implemented by igraph for this variant, so the scores are always raw
916 /// path counts. Time complexity: O(|S||E|), `S` being the source set.
917 ///
918 /// Binds [`igraph_betweenness_subset`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_betweenness_subset).
919 ///
920 /// # Errors
921 ///
922 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for an
923 /// invalid vertex in any of the selectors.
924 ///
925 /// # Examples
926 ///
927 /// ```
928 /// use igraph::prelude::*;
929 ///
930 /// // Directed path 0 -> 1 -> 2 -> 3, only paths from 0 to 3 count.
931 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, true).unwrap();
932 /// assert_eq!(g.betweenness_subset(None, 0, 3, .., true).unwrap(), vec![0.0, 1.0, 1.0, 0.0]);
933 /// // Undirected, the single pair {0, 3} counts 1/2.
934 /// let u = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
935 /// assert_eq!(u.betweenness_subset(None, 0, 3, .., false).unwrap(), vec![0.0, 0.5, 0.5, 0.0]);
936 /// // With all vertices as sources and targets it is the usual betweenness.
937 /// assert_eq!(u.betweenness_subset(None, .., .., .., false).unwrap(), u.betweenness(None, .., false, false).unwrap());
938 /// ```
939 pub fn betweenness_subset<'a>(
940 &self,
941 weights: Option<&[f64]>,
942 sources: impl Into<VertexSelector<'a>>,
943 targets: impl Into<VertexSelector<'a>>,
944 vids: impl Into<VertexSelector<'a>>,
945 directed: bool,
946 ) -> Result<Vec<f64>> {
947 let src = sources.into().to_raw()?;
948 let tgt = targets.into().to_raw()?;
949 let vs = vids.into().to_raw()?;
950 let w = weights_view(self, weights)?;
951 let mut res = Vector::new();
952 igraph_call!(igraph_betweenness_subset(
953 self,
954 ptr_of(&w),
955 &mut res,
956 src.get(),
957 tgt.get(),
958 vs.get(),
959 directed,
960 false
961 ))?;
962 Ok(res.into())
963 }
964
965 /// Betweenness centrality of the selected edges (`igraph_edge_betweenness`).
966 ///
967 /// The betweenness of an edge is the number of shortest paths passing
968 /// through it (fractionally, when there are several shortest paths).
969 /// Parameters are as in [`betweenness`](Self::betweenness); `eids` only
970 /// selects the returned scores. Removing the edge with the highest
971 /// betweenness is the basic step of the Girvan–Newman community detection
972 /// method, available as
973 /// [`community_edge_betweenness`](Self::community_edge_betweenness).
974 /// Time complexity: O(|V||E|).
975 ///
976 /// Binds [`igraph_edge_betweenness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_edge_betweenness).
977 ///
978 /// # Errors
979 ///
980 /// [`ErrorKind::InvalidEdgeId`](crate::ErrorKind::InvalidEdgeId) for an
981 /// invalid edge, [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
982 /// for invalid weights.
983 ///
984 /// # Examples
985 ///
986 /// ```
987 /// use igraph::prelude::*;
988 ///
989 /// // Two triangles joined by the bridge 2 - 3: all 9 cross pairs use it.
990 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3), (3, 4), (4, 5), (3, 5)], 6, false).unwrap();
991 /// let eb = g.edge_betweenness(None, .., false, false).unwrap();
992 /// assert_eq!(eb[3], 9.0);
993 /// ```
994 pub fn edge_betweenness<'a>(
995 &self,
996 weights: Option<&[f64]>,
997 eids: impl Into<EdgeSelector<'a>>,
998 directed: bool,
999 normalized: bool,
1000 ) -> Result<Vec<f64>> {
1001 let es = eids.into().to_raw()?;
1002 let w = weights_view(self, weights)?;
1003 let mut res = Vector::new();
1004 igraph_call!(igraph_edge_betweenness(
1005 self,
1006 ptr_of(&w),
1007 &mut res,
1008 es.get(),
1009 directed,
1010 normalized
1011 ))?;
1012 Ok(res.into())
1013 }
1014
1015 /// Range-limited edge betweenness (`igraph_edge_betweenness_cutoff`).
1016 ///
1017 /// Like [`edge_betweenness`](Self::edge_betweenness), counting only
1018 /// shortest paths of length at most `cutoff` (`None` = no limit).
1019 ///
1020 /// Binds [`igraph_edge_betweenness_cutoff`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_edge_betweenness_cutoff).
1021 ///
1022 /// # Errors
1023 ///
1024 /// As for [`edge_betweenness`](Self::edge_betweenness).
1025 ///
1026 /// # Examples
1027 ///
1028 /// ```
1029 /// use igraph::prelude::*;
1030 ///
1031 /// // With cutoff 1, every edge only carries the path between its endpoints.
1032 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1033 /// assert_eq!(g.edge_betweenness_cutoff(None, .., false, false, Some(1.0)).unwrap(), vec![1.0; 3]);
1034 /// ```
1035 pub fn edge_betweenness_cutoff<'a>(
1036 &self,
1037 weights: Option<&[f64]>,
1038 eids: impl Into<EdgeSelector<'a>>,
1039 directed: bool,
1040 normalized: bool,
1041 cutoff: Option<f64>,
1042 ) -> Result<Vec<f64>> {
1043 let es = eids.into().to_raw()?;
1044 let w = weights_view(self, weights)?;
1045 let mut res = Vector::new();
1046 igraph_call!(igraph_edge_betweenness_cutoff(
1047 self,
1048 ptr_of(&w),
1049 &mut res,
1050 es.get(),
1051 directed,
1052 normalized,
1053 raw_cutoff(cutoff)
1054 ))?;
1055 Ok(res.into())
1056 }
1057
1058 /// Edge betweenness restricted to paths between a set of sources and a
1059 /// set of targets (`igraph_edge_betweenness_subset`).
1060 ///
1061 /// Only shortest paths from `sources` to `targets` are counted; scores are
1062 /// returned for `eids`. As in [`betweenness_subset`](Self::betweenness_subset),
1063 /// in undirected graphs each source-target pair has weight 1/2.
1064 /// Normalization is not implemented by igraph for this variant. Time complexity: O(|S||E|).
1065 ///
1066 /// Binds [`igraph_edge_betweenness_subset`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_edge_betweenness_subset).
1067 ///
1068 /// # Errors
1069 ///
1070 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for an
1071 /// invalid source or target vertex.
1072 ///
1073 /// # Examples
1074 ///
1075 /// ```
1076 /// use igraph::prelude::*;
1077 ///
1078 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, true).unwrap();
1079 /// assert_eq!(g.edge_betweenness_subset(None, 0, 2, .., true).unwrap(), vec![1.0, 1.0, 0.0]);
1080 /// ```
1081 pub fn edge_betweenness_subset<'a>(
1082 &self,
1083 weights: Option<&[f64]>,
1084 sources: impl Into<VertexSelector<'a>>,
1085 targets: impl Into<VertexSelector<'a>>,
1086 eids: impl Into<EdgeSelector<'a>>,
1087 directed: bool,
1088 ) -> Result<Vec<f64>> {
1089 let src = sources.into().to_raw()?;
1090 let tgt = targets.into().to_raw()?;
1091 let es = eids.into().to_raw()?;
1092 let w = weights_view(self, weights)?;
1093 let mut res = Vector::new();
1094 igraph_call!(igraph_edge_betweenness_subset(
1095 self,
1096 ptr_of(&w),
1097 &mut res,
1098 src.get(),
1099 tgt.get(),
1100 es.get(),
1101 directed,
1102 false
1103 ))?;
1104 Ok(res.into())
1105 }
1106
1107 // ------------------------------------------------------------------
1108 // PageRank
1109 // ------------------------------------------------------------------
1110
1111 /// Google PageRank of the selected vertices (`igraph_pagerank`).
1112 ///
1113 /// The PageRank of a vertex is the fraction of time a random walker
1114 /// spends on it. The walker follows out-edges with probabilities
1115 /// proportional to their `weights` (which must be non-negative), and at
1116 /// each step restarts from a uniformly random vertex with probability
1117 /// `1 - damping`; it also restarts when stuck in a sink vertex. Scores of
1118 /// all vertices sum to one. In undirected graphs PageRank tends to be
1119 /// proportional to degree as the damping approaches 1, so it is mostly
1120 /// useful for directed graphs. See [`PageRankOptions`] for the damping,
1121 /// directedness and algorithm.
1122 ///
1123 /// `vids` only selects the returned scores: all of them are computed
1124 /// anyway. Time complexity: usually O(|E|).
1125 ///
1126 /// Reference: S. Brin and L. Page, *The Anatomy of a Large-Scale
1127 /// Hypertextual Web Search Engine*, WWW7 (1998).
1128 ///
1129 /// Binds [`igraph_pagerank`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_pagerank).
1130 ///
1131 /// # Errors
1132 ///
1133 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for an
1134 /// invalid vertex, [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
1135 /// for a damping outside `[0, 1]` (or `NaN`) or invalid (negative, `NaN`
1136 /// or infinite) weights. These are checked on the Rust side: igraph lets
1137 /// a `NaN` damping and infinite weights through, and then either aborts
1138 /// the process (ARPACK) or returns `NaN` scores (PRPACK). Huge finite
1139 /// weights are rescaled (PageRank does not depend on the scale of the
1140 /// weights).
1141 ///
1142 /// # Examples
1143 ///
1144 /// ```
1145 /// use igraph::prelude::*;
1146 /// use igraph::centrality::PageRankOptions;
1147 ///
1148 /// // A directed cycle: by symmetry every vertex has PageRank 1/4.
1149 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], 4, true).unwrap();
1150 /// let pr = g.pagerank(None, .., &PageRankOptions::default()).unwrap();
1151 /// assert_eq!(pr.value, 1.0);
1152 /// assert!(pr.scores.iter().all(|&p| (p - 0.25).abs() < 1e-12));
1153 /// ```
1154 pub fn pagerank<'a>(
1155 &self,
1156 weights: Option<&[f64]>,
1157 vids: impl Into<VertexSelector<'a>>,
1158 options: &PageRankOptions,
1159 ) -> Result<EigenScores> {
1160 let vs = vids.into().to_raw()?;
1161 check_damping(options.damping)?;
1162 let (scaled, _) = spectral_weights(weights)?;
1163 let w = weights_view(self, scaled.as_deref().or(weights))?;
1164 let mut res = Vector::new();
1165 let mut value = 0.0;
1166 // ARPACK keeps thread-local state: refuse to nest it (e.g. from an
1167 // interruption handler running inside another ARPACK computation).
1168 let _arpack = if options.algo == PageRankAlgo::Arpack {
1169 Some(crate::linalg::ArpackGuard::enter()?)
1170 } else {
1171 None
1172 };
1173 igraph_call!(igraph_pagerank(
1174 self,
1175 ptr_of(&w),
1176 &mut res,
1177 &mut value,
1178 options.damping,
1179 options.directed,
1180 vs.get(),
1181 options.algo.into(),
1182 std::ptr::null_mut()
1183 ))?;
1184 Ok(EigenScores {
1185 scores: res.into(),
1186 value,
1187 })
1188 }
1189
1190 /// Personalized PageRank with an arbitrary restart distribution
1191 /// (`igraph_personalized_pagerank`).
1192 ///
1193 /// Like [`pagerank`](Self::pagerank), but when the random walker restarts
1194 /// (with probability `1 - damping`, or when stuck in a sink), the new
1195 /// starting vertex is drawn from the distribution `reset` (one
1196 /// non-negative entry per vertex, not necessarily normalized) instead of
1197 /// uniformly. `reset = None` gives the ordinary PageRank.
1198 ///
1199 /// Binds [`igraph_personalized_pagerank`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_personalized_pagerank).
1200 ///
1201 /// # Errors
1202 ///
1203 /// As for [`pagerank`](Self::pagerank); also
1204 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `reset`
1205 /// has the wrong length, negative entries or sums to zero.
1206 ///
1207 /// # Examples
1208 ///
1209 /// ```
1210 /// use igraph::prelude::*;
1211 /// use igraph::centrality::PageRankOptions;
1212 ///
1213 /// // Always restarting from the leaf 0 of a path breaks its symmetry.
1214 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1215 /// let reset = [1.0, 0.0, 0.0, 0.0];
1216 /// let pr = g.personalized_pagerank(None, Some(&reset), .., &PageRankOptions::default()).unwrap();
1217 /// assert!(pr.scores[0] > pr.scores[3] && pr.scores[1] > pr.scores[2]);
1218 /// ```
1219 pub fn personalized_pagerank<'a>(
1220 &self,
1221 weights: Option<&[f64]>,
1222 reset: Option<&[f64]>,
1223 vids: impl Into<VertexSelector<'a>>,
1224 options: &PageRankOptions,
1225 ) -> Result<EigenScores> {
1226 if let Some(r) = reset
1227 && r.len() != self.vcount()
1228 {
1229 return Err(Error::invalid(format!(
1230 "the reset vector has length {}, but the graph has {} vertices",
1231 r.len(),
1232 self.vcount()
1233 )));
1234 }
1235 let vs = vids.into().to_raw()?;
1236 check_damping(options.damping)?;
1237 let (scaled, _) = spectral_weights(weights)?;
1238 let w = weights_view(self, scaled.as_deref().or(weights))?;
1239 let r = reset.map(Vector::view);
1240 let mut res = Vector::new();
1241 let mut value = 0.0;
1242 // ARPACK keeps thread-local state: refuse to nest it (e.g. from an
1243 // interruption handler running inside another ARPACK computation).
1244 let _arpack = if options.algo == PageRankAlgo::Arpack {
1245 Some(crate::linalg::ArpackGuard::enter()?)
1246 } else {
1247 None
1248 };
1249 igraph_call!(igraph_personalized_pagerank(
1250 self,
1251 ptr_of(&w),
1252 &mut res,
1253 &mut value,
1254 ptr_of(&r),
1255 options.damping,
1256 options.directed,
1257 vs.get(),
1258 options.algo.into(),
1259 std::ptr::null_mut()
1260 ))?;
1261 Ok(EigenScores {
1262 scores: res.into(),
1263 value,
1264 })
1265 }
1266
1267 /// Personalized PageRank restarting from a set of vertices
1268 /// (`igraph_personalized_pagerank_vs`).
1269 ///
1270 /// Like [`personalized_pagerank`](Self::personalized_pagerank), with the
1271 /// restart vertex chosen uniformly among `reset_vids` (duplicates count
1272 /// multiple times). Restarting always from a single vertex gives a
1273 /// "proximity to this vertex" measure, widely used for recommendations.
1274 ///
1275 /// Binds [`igraph_personalized_pagerank_vs`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_personalized_pagerank_vs).
1276 ///
1277 /// # Errors
1278 ///
1279 /// As for [`pagerank`](Self::pagerank); an empty or invalid `reset_vids`
1280 /// is an error too.
1281 ///
1282 /// # Examples
1283 ///
1284 /// ```
1285 /// use igraph::prelude::*;
1286 /// use igraph::centrality::PageRankOptions;
1287 ///
1288 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1289 /// let pr = g.personalized_pagerank_vs(None, 3, .., &PageRankOptions::default()).unwrap();
1290 /// assert!(pr.scores[3] > pr.scores[0]);
1291 /// ```
1292 pub fn personalized_pagerank_vs<'a>(
1293 &self,
1294 weights: Option<&[f64]>,
1295 reset_vids: impl Into<VertexSelector<'a>>,
1296 vids: impl Into<VertexSelector<'a>>,
1297 options: &PageRankOptions,
1298 ) -> Result<EigenScores> {
1299 let reset = reset_vids.into().to_raw()?;
1300 let vs = vids.into().to_raw()?;
1301 check_damping(options.damping)?;
1302 let (scaled, _) = spectral_weights(weights)?;
1303 let w = weights_view(self, scaled.as_deref().or(weights))?;
1304 let mut res = Vector::new();
1305 let mut value = 0.0;
1306 // ARPACK keeps thread-local state: refuse to nest it (e.g. from an
1307 // interruption handler running inside another ARPACK computation).
1308 let _arpack = if options.algo == PageRankAlgo::Arpack {
1309 Some(crate::linalg::ArpackGuard::enter()?)
1310 } else {
1311 None
1312 };
1313 igraph_call!(igraph_personalized_pagerank_vs(
1314 self,
1315 ptr_of(&w),
1316 &mut res,
1317 &mut value,
1318 reset.get(),
1319 options.damping,
1320 options.directed,
1321 vs.get(),
1322 options.algo.into(),
1323 std::ptr::null_mut()
1324 ))?;
1325 Ok(EigenScores {
1326 scores: res.into(),
1327 value,
1328 })
1329 }
1330
1331 // ------------------------------------------------------------------
1332 // Spectral centralities
1333 // ------------------------------------------------------------------
1334
1335 /// Eigenvector centrality of all vertices (`igraph_eigenvector_centrality`).
1336 ///
1337 /// The eigenvector centrality of a vertex is proportional to the sum of
1338 /// the centralities of its neighbors: it is the eigenvector of the
1339 /// adjacency matrix belonging to the largest positive eigenvalue, which is
1340 /// non-negative when weights are non-negative. Scores are scaled so that
1341 /// the maximum is 1 (unless all are zero). In undirected graphs a self-loop
1342 /// counts twice on the diagonal; weights of parallel edges add up.
1343 ///
1344 /// `mode` matters for directed graphs only: [`NeighborMode::Out`] (the
1345 /// standard choice) gives each vertex the sum of the centralities of the
1346 /// vertices *pointing to it* (left eigenvector); [`NeighborMode::In`] the
1347 /// sum over the vertices it points to; [`NeighborMode::All`] ignores
1348 /// directions.
1349 ///
1350 /// The measure is meaningful only for (strongly) connected graphs: in a
1351 /// disconnected undirected graph all but one component typically get
1352 /// zeros (igraph emits a warning). Directed acyclic graphs have no positive
1353 /// eigenvalue: the returned [`value`](EigenScores::value) is then zero.
1354 /// For directed graphs, consider
1355 /// [`hub_and_authority_scores`](Self::hub_and_authority_scores). Time
1356 /// complexity: usually O(|V| + |E|).
1357 ///
1358 /// See also [`connected_components`](Self::connected_components) to split
1359 /// a disconnected graph first (and [`is_dag`](Self::is_dag) to detect the
1360 /// acyclic case), and [`eigen_adjacency`](Self::eigen_adjacency) for other
1361 /// eigenpairs of the adjacency matrix.
1362 ///
1363 /// Binds [`igraph_eigenvector_centrality`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_eigenvector_centrality).
1364 ///
1365 /// # Errors
1366 ///
1367 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for invalid
1368 /// weights (wrong length, `NaN` or infinite: igraph does not check the
1369 /// latter and ARPACK would abort the process);
1370 /// [`ErrorKind::Arpack`](crate::ErrorKind::Arpack) if the eigensolver
1371 /// fails. Weights so large that the iterations could overflow (absolute
1372 /// sum above `1e150`) are divided by a power of two before the call, and
1373 /// the eigenvalue is scaled back (it may then be infinite, when it is not
1374 /// representable): the scores do not depend on the scale of the weights.
1375 ///
1376 /// # Examples
1377 ///
1378 /// ```
1379 /// use igraph::prelude::*;
1380 ///
1381 /// // Weighted star with center 0 and weights 1..9 (igraph's example).
1382 /// let edges: Vec<(i64, i64)> = (1..10).map(|i| (0, i)).collect();
1383 /// let g = Graph::from_edges(&edges, 10, false).unwrap();
1384 /// let w: Vec<f64> = (1..10).map(f64::from).collect();
1385 /// let ec = g.eigenvector_centrality(NeighborMode::Out, Some(&w)).unwrap();
1386 /// assert!((ec.value - 16.8819).abs() < 1e-4);
1387 /// assert_eq!(ec.scores[0], 1.0);
1388 /// assert!((ec.scores[1] - 0.0592349).abs() < 1e-6);
1389 /// ```
1390 pub fn eigenvector_centrality(
1391 &self,
1392 mode: NeighborMode,
1393 weights: Option<&[f64]>,
1394 ) -> Result<EigenScores> {
1395 let (scaled, scale) = spectral_weights(weights)?;
1396 let w = weights_view(self, scaled.as_deref().or(weights))?;
1397 let mut res = Vector::new();
1398 let mut value = 0.0;
1399 // ARPACK keeps thread-local state: refuse to nest it.
1400 let _arpack = crate::linalg::ArpackGuard::enter()?;
1401 igraph_call!(igraph_eigenvector_centrality(
1402 self,
1403 &mut res,
1404 &mut value,
1405 mode.into(),
1406 ptr_of(&w),
1407 std::ptr::null_mut()
1408 ))?;
1409 Ok(EigenScores {
1410 scores: res.into(),
1411 value: value * scale,
1412 })
1413 }
1414
1415 /// Kleinberg's hub and authority scores (HITS)
1416 /// (`igraph_hub_and_authority_scores`).
1417 ///
1418 /// The authority score of a vertex is proportional to the sum of the hub
1419 /// scores of the vertices pointing to it, and its hub score to the sum of
1420 /// the authority scores of the vertices it points to. Hubs and authorities
1421 /// are the principal eigenvectors of `A Aᵀ` and `Aᵀ A`; igraph guarantees
1422 /// that the two returned vectors match (`h = A a`, `a = Aᵀ h`, up to
1423 /// scaling) even when the eigenvalue is degenerate. Both are scaled to
1424 /// have maximum 1. Edge weights should be non-negative (igraph warns
1425 /// otherwise).
1426 ///
1427 /// In undirected graphs both vectors coincide with the [eigenvector
1428 /// centrality](Self::eigenvector_centrality) (computed by it directly,
1429 /// with a warning) and [`value`](HubAuthority::value) is the square of its
1430 /// eigenvalue. A graph without edges gives all-ones scores and value 0.
1431 ///
1432 /// In extremely sparse graphs, where no single connected component
1433 /// dominates the graphs of `A Aᵀ` and `Aᵀ A`, the solution is not unique
1434 /// and many scores are zero: igraph then emits a warning (retrieve it with
1435 /// [`take_warnings`](crate::error::take_warnings)) when *more than 30%* of
1436 /// the hub scores are zero (below `10 ε` in absolute value), on directed
1437 /// graphs with at least 10 vertices. (In igraph 1.0.0 a rounding bug made
1438 /// it warn for any zero score; this was fixed in 1.0.1.)
1439 ///
1440 /// Time complexity: usually O(|V|).
1441 ///
1442 /// Reference: J. Kleinberg, *Authoritative sources in a hyperlinked
1443 /// environment*, J. ACM 46 (1999).
1444 ///
1445 /// See also [`pagerank`](Self::pagerank), another random-walk based
1446 /// ranking for directed graphs.
1447 ///
1448 /// Binds [`igraph_hub_and_authority_scores`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_hub_and_authority_scores).
1449 ///
1450 /// # Errors
1451 ///
1452 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for invalid
1453 /// weights (wrong length, `NaN` or infinite: igraph does not check the
1454 /// latter and ARPACK would abort the process);
1455 /// [`ErrorKind::Arpack`](crate::ErrorKind::Arpack) if the eigensolver
1456 /// fails. Weights so large that the iterations could overflow (absolute
1457 /// sum above `1e150`) are divided by a power of two before the call, and
1458 /// the eigenvalue is scaled back (it may then be infinite, when it is not
1459 /// representable): the scores do not depend on the scale of the weights.
1460 ///
1461 /// # Examples
1462 ///
1463 /// ```
1464 /// use igraph::prelude::*;
1465 ///
1466 /// // Two pages linking to a third one: two perfect hubs, one authority.
1467 /// let g = Graph::from_edges(&[(0, 2), (1, 2)], 3, true).unwrap();
1468 /// let hits = g.hub_and_authority_scores(None).unwrap();
1469 /// assert_eq!(hits.hubs, vec![1.0, 1.0, 0.0]);
1470 /// assert_eq!(hits.authorities, vec![0.0, 0.0, 1.0]);
1471 /// assert!((hits.value - 2.0).abs() < 1e-9);
1472 /// ```
1473 pub fn hub_and_authority_scores(&self, weights: Option<&[f64]>) -> Result<HubAuthority> {
1474 let (scaled, scale) = spectral_weights(weights)?;
1475 let w = weights_view(self, scaled.as_deref().or(weights))?;
1476 let mut hubs = Vector::new();
1477 let mut auth = Vector::new();
1478 // Always request `value`: for undirected graphs igraph (1.0.0 and
1479 // 1.0.1) squares `*value` without checking it for NULL.
1480 let mut value = 0.0;
1481 // ARPACK keeps thread-local state: refuse to nest it.
1482 let _arpack = crate::linalg::ArpackGuard::enter()?;
1483 igraph_call!(igraph_hub_and_authority_scores(
1484 self,
1485 &mut hubs,
1486 &mut auth,
1487 &mut value,
1488 ptr_of(&w),
1489 std::ptr::null_mut()
1490 ))?;
1491 Ok(HubAuthority {
1492 hubs: hubs.into(),
1493 authorities: auth.into(),
1494 // The eigenvalue of `A Aᵀ` scales quadratically.
1495 value: value * scale * scale,
1496 })
1497 }
1498
1499 // ------------------------------------------------------------------
1500 // Structural holes and convergence
1501 // ------------------------------------------------------------------
1502
1503 /// Burt's constraint scores of the selected vertices (`igraph_constraint`).
1504 ///
1505 /// Constraint measures how much a vertex's ego network is closed: it is
1506 /// high when ego has few, or mutually strongly related (redundant),
1507 /// contacts, and low for vertices bridging *structural holes*. Formally
1508 /// `C[i] = Σ_{j ≠ i} (p[i,j] + Σ_{q ≠ i,j} p[i,q] p[q,j])²` over the
1509 /// neighbors `j`, with proportional tie strengths
1510 /// `p[i,j] = (a[i,j] + a[j,i]) / Σ_k (a[i,k] + a[k,i])`, `a` being the
1511 /// (weighted) adjacency matrix. It is undefined (NaN) for isolated
1512 /// vertices. Time complexity: O(|V| + |E| + n d²), `d` the average degree.
1513 ///
1514 /// Reference: R. S. Burt, *Structural holes and good ideas*, American
1515 /// Journal of Sociology 110, 349–399 (2004).
1516 ///
1517 /// See also [`transitivity_local_undirected`](Self::transitivity_local_undirected),
1518 /// the local clustering coefficient, a related measure of ego-network
1519 /// closure.
1520 ///
1521 /// Binds [`igraph_constraint`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_constraint).
1522 ///
1523 /// # Errors
1524 ///
1525 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for an
1526 /// invalid vertex, [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
1527 /// for weights of the wrong length.
1528 ///
1529 /// # Examples
1530 ///
1531 /// ```
1532 /// use igraph::prelude::*;
1533 ///
1534 /// // In a path, the end vertices are fully constrained by their only contact.
1535 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1536 /// assert_eq!(g.constraint(.., None).unwrap(), vec![1.0, 0.5, 0.5, 1.0]);
1537 /// ```
1538 pub fn constraint<'a>(
1539 &self,
1540 vids: impl Into<VertexSelector<'a>>,
1541 weights: Option<&[f64]>,
1542 ) -> Result<Vec<f64>> {
1543 let vs = vids.into().to_raw()?;
1544 let w = weights_view(self, weights)?;
1545 let mut res = Vector::new();
1546 igraph_call!(igraph_constraint(self, &mut res, vs.get(), ptr_of(&w)))?;
1547 Ok(res.into())
1548 }
1549
1550 /// Convergence degree of every edge (`igraph_convergence_degree`).
1551 ///
1552 /// The *input set* of an edge is the set of vertices where the shortest
1553 /// paths passing through it originate, the *output set* where they
1554 /// terminate. The convergence degree is `(|in| − |out|) / (|in| + |out|)`,
1555 /// in `(-1, 1)`: positive values mark *convergent* edges (paths coming
1556 /// from many vertices and going to few), negative ones *divergent* edges.
1557 /// In undirected graphs the edge is oriented arbitrarily and the absolute
1558 /// value is reported. Time complexity: O(|V||E|).
1559 ///
1560 /// Binds [`igraph_convergence_degree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_convergence_degree).
1561 ///
1562 /// # Examples
1563 ///
1564 /// ```
1565 /// use igraph::prelude::*;
1566 ///
1567 /// // An in-star 1,2,3,4 -> 0 followed by 0 -> 5 (igraph's unit test).
1568 /// let g = Graph::from_edges(&[(1, 0), (2, 0), (3, 0), (4, 0), (0, 5)], 6, true).unwrap();
1569 /// let cd = g.convergence_degree().unwrap();
1570 /// assert!((cd.result[4] - 2.0 / 3.0).abs() < 1e-9);
1571 /// ```
1572 pub fn convergence_degree(&self) -> Result<ConvergenceDegree> {
1573 let mut result = Vector::new();
1574 let mut ins = Vector::new();
1575 let mut outs = Vector::new();
1576 igraph_call!(igraph_convergence_degree(
1577 self,
1578 &mut result,
1579 &mut ins,
1580 &mut outs
1581 ))?;
1582 Ok(ConvergenceDegree {
1583 result: result.into(),
1584 ins: ins.into(),
1585 outs: outs.into(),
1586 })
1587 }
1588
1589 // ------------------------------------------------------------------
1590 // Centralization
1591 // ------------------------------------------------------------------
1592
1593 /// Degree centralization of the graph (`igraph_centralization_degree`).
1594 ///
1595 /// Computes the degrees of all vertices (with `mode` for directed graphs
1596 /// and the `loops` counting convention, see [`Loops`]) and their
1597 /// [`centralization`] index, normalized by the theoretical maximum (the
1598 /// star) if `normalized` is true. Time complexity: O(|V| + |E|).
1599 ///
1600 /// See also [`degree`](Self::degree) and [`maxdegree`](Self::maxdegree).
1601 ///
1602 /// Binds [`igraph_centralization_degree`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_degree).
1603 ///
1604 /// # Examples
1605 ///
1606 /// ```
1607 /// use igraph::prelude::*;
1608 ///
1609 /// // A cycle is perfectly decentralized.
1610 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], 4, false).unwrap();
1611 /// let c = g.centralization_degree(NeighborMode::All, Loops::None, true).unwrap();
1612 /// assert_eq!(c.scores, vec![2.0; 4]);
1613 /// assert_eq!(c.centralization, 0.0);
1614 /// assert_eq!(c.theoretical_max, 6.0);
1615 /// ```
1616 pub fn centralization_degree(
1617 &self,
1618 mode: NeighborMode,
1619 loops: Loops,
1620 normalized: bool,
1621 ) -> Result<Centralization> {
1622 let mut res = Vector::new();
1623 let (mut cent, mut tmax) = (0.0, 0.0);
1624 igraph_call!(igraph_centralization_degree(
1625 self,
1626 &mut res,
1627 mode.into(),
1628 loops.into(),
1629 &mut cent,
1630 &mut tmax,
1631 normalized
1632 ))?;
1633 Ok(Centralization {
1634 scores: res.into(),
1635 centralization: cent,
1636 theoretical_max: tmax,
1637 })
1638 }
1639
1640 /// Theoretical maximum of degree centralization for graphs with the
1641 /// size and directedness of `self` (`igraph_centralization_degree_tmax`).
1642 ///
1643 /// `mode` is ignored for undirected graphs. See the free function
1644 /// [`centralization_degree_tmax`] to specify the number of vertices
1645 /// directly.
1646 ///
1647 /// Binds [`igraph_centralization_degree_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_degree_tmax).
1648 pub fn centralization_degree_tmax(&self, mode: NeighborMode, loops: Loops) -> Result<f64> {
1649 let mut res = 0.0;
1650 igraph_call!(igraph_centralization_degree_tmax(
1651 self,
1652 0,
1653 mode.into(),
1654 loops.into(),
1655 &mut res
1656 ))?;
1657 Ok(res)
1658 }
1659
1660 /// Betweenness centralization of the graph
1661 /// (`igraph_centralization_betweenness`).
1662 ///
1663 /// Computes the (unweighted) [`betweenness`](Self::betweenness) of all
1664 /// vertices, following directions if `directed`, and its
1665 /// [`centralization`] index, normalized by the theoretical maximum (the
1666 /// star) if `normalized`. Time complexity: O(|V||E|).
1667 ///
1668 /// Binds [`igraph_centralization_betweenness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_betweenness).
1669 ///
1670 /// # Examples
1671 ///
1672 /// ```
1673 /// use igraph::prelude::*;
1674 ///
1675 /// let star = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], 5, false).unwrap();
1676 /// let c = star.centralization_betweenness(false, true).unwrap();
1677 /// assert_eq!(c.centralization, 1.0);
1678 /// ```
1679 pub fn centralization_betweenness(
1680 &self,
1681 directed: bool,
1682 normalized: bool,
1683 ) -> Result<Centralization> {
1684 let mut res = Vector::new();
1685 let (mut cent, mut tmax) = (0.0, 0.0);
1686 igraph_call!(igraph_centralization_betweenness(
1687 self, &mut res, directed, &mut cent, &mut tmax, normalized
1688 ))?;
1689 Ok(Centralization {
1690 scores: res.into(),
1691 centralization: cent,
1692 theoretical_max: tmax,
1693 })
1694 }
1695
1696 /// Theoretical maximum of betweenness centralization for graphs with the
1697 /// size and directedness of `self`
1698 /// (`igraph_centralization_betweenness_tmax`).
1699 ///
1700 /// `directed` is ignored for undirected graphs. See the free function
1701 /// [`centralization_betweenness_tmax`] to give the number of vertices.
1702 ///
1703 /// Binds [`igraph_centralization_betweenness_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_betweenness_tmax).
1704 pub fn centralization_betweenness_tmax(&self, directed: bool) -> Result<f64> {
1705 let mut res = 0.0;
1706 igraph_call!(igraph_centralization_betweenness_tmax(
1707 self, 0, directed, &mut res
1708 ))?;
1709 Ok(res)
1710 }
1711
1712 /// Closeness centralization of the graph
1713 /// (`igraph_centralization_closeness`).
1714 ///
1715 /// Computes the (unweighted, normalized) [`closeness`](Self::closeness)
1716 /// of all vertices, using `mode` for directed graphs, and its
1717 /// [`centralization`] index, normalized by the theoretical maximum (the
1718 /// star) if `normalized`. Time complexity: O(|V||E|).
1719 ///
1720 /// Binds [`igraph_centralization_closeness`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_closeness).
1721 ///
1722 /// # Examples
1723 ///
1724 /// ```
1725 /// use igraph::prelude::*;
1726 ///
1727 /// let star = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], 5, false).unwrap();
1728 /// let c = star.centralization_closeness(NeighborMode::All, true).unwrap();
1729 /// assert!((c.centralization - 1.0).abs() < 1e-12);
1730 /// ```
1731 pub fn centralization_closeness(
1732 &self,
1733 mode: NeighborMode,
1734 normalized: bool,
1735 ) -> Result<Centralization> {
1736 let mut res = Vector::new();
1737 let (mut cent, mut tmax) = (0.0, 0.0);
1738 igraph_call!(igraph_centralization_closeness(
1739 self,
1740 &mut res,
1741 mode.into(),
1742 &mut cent,
1743 &mut tmax,
1744 normalized
1745 ))?;
1746 Ok(Centralization {
1747 scores: res.into(),
1748 centralization: cent,
1749 theoretical_max: tmax,
1750 })
1751 }
1752
1753 /// Theoretical maximum of closeness centralization for graphs with the
1754 /// size and directedness of `self` (`igraph_centralization_closeness_tmax`).
1755 ///
1756 /// `mode` is ignored for undirected graphs. See the free function
1757 /// [`centralization_closeness_tmax`] to give the number of vertices.
1758 ///
1759 /// Binds [`igraph_centralization_closeness_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_closeness_tmax).
1760 pub fn centralization_closeness_tmax(&self, mode: NeighborMode) -> Result<f64> {
1761 let mut res = 0.0;
1762 igraph_call!(igraph_centralization_closeness_tmax(
1763 self,
1764 0,
1765 mode.into(),
1766 &mut res
1767 ))?;
1768 Ok(res)
1769 }
1770
1771 /// Eigenvector centralization of the graph
1772 /// (`igraph_centralization_eigenvector_centrality`).
1773 ///
1774 /// Computes the (unweighted) [eigenvector
1775 /// centrality](Self::eigenvector_centrality) of all vertices, scaled so
1776 /// that the maximum is 1, and its [`centralization`] index, normalized by
1777 /// the theoretical maximum if `normalized`. Note that eigenvector scores
1778 /// have no natural scale, so the centralization depends on the choice of
1779 /// scaling by the maximum (∞-norm). The most centralized undirected graph
1780 /// is a single edge (plus isolated vertices). `mode` is as in
1781 /// [`eigenvector_centrality`](Self::eigenvector_centrality).
1782 ///
1783 /// Binds [`igraph_centralization_eigenvector_centrality`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_eigenvector_centrality).
1784 ///
1785 /// # Errors
1786 ///
1787 /// [`ErrorKind::Arpack`](crate::ErrorKind::Arpack) if the eigensolver fails.
1788 ///
1789 /// # Examples
1790 ///
1791 /// ```
1792 /// use igraph::prelude::*;
1793 ///
1794 /// let g = Graph::from_edges(&[(0, 1)], 10, false).unwrap();
1795 /// let c = g.centralization_eigenvector_centrality(NeighborMode::All, true).unwrap();
1796 /// assert!((c.centralization - 1.0).abs() < 1e-9);
1797 /// ```
1798 pub fn centralization_eigenvector_centrality(
1799 &self,
1800 mode: NeighborMode,
1801 normalized: bool,
1802 ) -> Result<EigenvectorCentralization> {
1803 let mut res = Vector::new();
1804 let (mut value, mut cent, mut tmax) = (0.0, 0.0, 0.0);
1805 // ARPACK keeps thread-local state: refuse to nest it.
1806 let _arpack = crate::linalg::ArpackGuard::enter()?;
1807 igraph_call!(igraph_centralization_eigenvector_centrality(
1808 self,
1809 &mut res,
1810 &mut value,
1811 mode.into(),
1812 std::ptr::null_mut(),
1813 &mut cent,
1814 &mut tmax,
1815 normalized
1816 ))?;
1817 Ok(EigenvectorCentralization {
1818 scores: res.into(),
1819 value,
1820 centralization: cent,
1821 theoretical_max: tmax,
1822 })
1823 }
1824
1825 /// Theoretical maximum of eigenvector centralization for graphs with the
1826 /// size and directedness of `self`
1827 /// (`igraph_centralization_eigenvector_centrality_tmax`).
1828 ///
1829 /// `mode` is ignored for undirected graphs. See the free function
1830 /// [`centralization_eigenvector_centrality_tmax`] to give the number of
1831 /// vertices.
1832 ///
1833 /// Binds [`igraph_centralization_eigenvector_centrality_tmax`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_centralization_eigenvector_centrality_tmax).
1834 pub fn centralization_eigenvector_centrality_tmax(&self, mode: NeighborMode) -> Result<f64> {
1835 let mut res = 0.0;
1836 igraph_call!(igraph_centralization_eigenvector_centrality_tmax(
1837 self,
1838 0,
1839 mode.into(),
1840 &mut res
1841 ))?;
1842 Ok(res)
1843 }
1844
1845 // ------------------------------------------------------------------
1846 // Local scan statistics
1847 // ------------------------------------------------------------------
1848
1849 /// Local scan statistic with `k = 0` (`igraph_local_scan_0`).
1850 ///
1851 /// By convention the 0-scan of a vertex is its degree (`weights = None`)
1852 /// or its strength (the sum of incident edge weights, as computed by
1853 /// [`strength`](Self::strength)). `mode` selects out-, in- or all edges in
1854 /// directed graphs.
1855 ///
1856 /// Reference: C. E. Priebe et al., *Scan Statistics on Enron Graphs*,
1857 /// Comput. Math. Organ. Theory (2005).
1858 ///
1859 /// Binds [`igraph_local_scan_0`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_0).
1860 ///
1861 /// # Examples
1862 ///
1863 /// ```
1864 /// use igraph::prelude::*;
1865 ///
1866 /// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1867 /// assert_eq!(g.local_scan_0(None, NeighborMode::All).unwrap(), vec![1.0, 2.0, 1.0]);
1868 /// assert_eq!(g.local_scan_0(Some(&[0.5, 2.0]), NeighborMode::All).unwrap(), vec![0.5, 2.5, 2.0]);
1869 /// ```
1870 pub fn local_scan_0(&self, weights: Option<&[f64]>, mode: NeighborMode) -> Result<Vec<f64>> {
1871 let w = weights_view(self, weights)?;
1872 let mut res = Vector::new();
1873 igraph_call!(igraph_local_scan_0(self, &mut res, ptr_of(&w), mode.into()))?;
1874 Ok(res.into())
1875 }
1876
1877 /// "Them" local scan statistic with `k = 0` (`igraph_local_scan_0_them`).
1878 ///
1879 /// Neighborhoods are taken from `self` ("us"), but edges are counted (or
1880 /// their `weights_them` summed) in the graph `them`, which must have the
1881 /// same vertices and directedness: the result is, for every vertex, the
1882 /// number of `them` edges incident to it that also connect it in `us`.
1883 /// Useful to compare two snapshots of a network.
1884 ///
1885 /// Binds [`igraph_local_scan_0_them`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_0_them).
1886 ///
1887 /// # Errors
1888 ///
1889 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the two
1890 /// graphs differ in size or directedness, or `weights_them` does not have
1891 /// one entry per edge of `them`.
1892 ///
1893 /// # Examples
1894 ///
1895 /// ```
1896 /// use igraph::prelude::*;
1897 ///
1898 /// let us = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1899 /// let them = Graph::from_edges(&[(0, 1), (0, 2)], 3, false).unwrap();
1900 /// assert_eq!(us.local_scan_0_them(&them, None, NeighborMode::All).unwrap(), vec![1.0, 1.0, 0.0]);
1901 /// ```
1902 pub fn local_scan_0_them(
1903 &self,
1904 them: &Graph,
1905 weights_them: Option<&[f64]>,
1906 mode: NeighborMode,
1907 ) -> Result<Vec<f64>> {
1908 check_them(self, them)?;
1909 let w = weights_view(them, weights_them)?;
1910 let mut res = Vector::new();
1911 igraph_call!(igraph_local_scan_0_them(
1912 self,
1913 them,
1914 &mut res,
1915 ptr_of(&w),
1916 mode.into()
1917 ))?;
1918 Ok(res.into())
1919 }
1920
1921 /// Local scan statistic with `k = 1` (`igraph_local_scan_1_ecount`).
1922 ///
1923 /// For every vertex, the number of edges (or the sum of their weights) in
1924 /// the subgraph induced by its closed 1-neighborhood (the vertex and its
1925 /// neighbors along `mode`). For undirected simple graphs this is
1926 /// `degree + number of triangles` through the vertex (see
1927 /// [`count_adjacent_triangles`](Self::count_adjacent_triangles)).
1928 ///
1929 /// Binds [`igraph_local_scan_1_ecount`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_1_ecount).
1930 ///
1931 /// # Examples
1932 ///
1933 /// ```
1934 /// use igraph::prelude::*;
1935 ///
1936 /// // A triangle with a pendant vertex 3 attached to 2.
1937 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], 4, false).unwrap();
1938 /// assert_eq!(g.local_scan_1_ecount(None, NeighborMode::All).unwrap(), vec![3.0, 3.0, 4.0, 1.0]);
1939 /// ```
1940 pub fn local_scan_1_ecount(
1941 &self,
1942 weights: Option<&[f64]>,
1943 mode: NeighborMode,
1944 ) -> Result<Vec<f64>> {
1945 let w = weights_view(self, weights)?;
1946 let mut res = Vector::new();
1947 igraph_call!(igraph_local_scan_1_ecount(
1948 self,
1949 &mut res,
1950 ptr_of(&w),
1951 mode.into()
1952 ))?;
1953 Ok(res.into())
1954 }
1955
1956 /// "Them" local scan statistic with `k = 1`
1957 /// (`igraph_local_scan_1_ecount_them`).
1958 ///
1959 /// For every vertex, the number of edges of `them` (or the sum of their
1960 /// `weights_them`) inside the closed 1-neighborhood of the vertex in
1961 /// `self`. The graphs must have the same vertices and directedness.
1962 ///
1963 /// Binds [`igraph_local_scan_1_ecount_them`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_1_ecount_them).
1964 ///
1965 /// # Errors
1966 ///
1967 /// As for [`local_scan_0_them`](Self::local_scan_0_them).
1968 pub fn local_scan_1_ecount_them(
1969 &self,
1970 them: &Graph,
1971 weights_them: Option<&[f64]>,
1972 mode: NeighborMode,
1973 ) -> Result<Vec<f64>> {
1974 check_them(self, them)?;
1975 let w = weights_view(them, weights_them)?;
1976 let mut res = Vector::new();
1977 self.with_fresh_multi_cache(|| {
1978 igraph_call!(igraph_local_scan_1_ecount_them(
1979 self,
1980 them,
1981 &mut res,
1982 ptr_of(&w),
1983 mode.into()
1984 ))
1985 })?;
1986 Ok(res.into())
1987 }
1988
1989 /// Local scan statistic for `k`-neighborhoods (`igraph_local_scan_k_ecount`).
1990 ///
1991 /// For every vertex, the number of edges (or the sum of their weights) in
1992 /// the subgraph induced by the vertices within distance `k` along `mode`.
1993 /// `k = 0` is special-cased to [`local_scan_0`](Self::local_scan_0) (degree
1994 /// or strength), `k = 1` to [`local_scan_1_ecount`](Self::local_scan_1_ecount).
1995 /// The neighborhoods themselves are given by
1996 /// [`neighborhood`](Self::neighborhood) with `order = k`.
1997 ///
1998 /// Binds [`igraph_local_scan_k_ecount`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_k_ecount).
1999 ///
2000 /// # Errors
2001 ///
2002 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for weights
2003 /// of the wrong length.
2004 ///
2005 /// # Examples
2006 ///
2007 /// ```
2008 /// use igraph::prelude::*;
2009 ///
2010 /// // In a path of 5 vertices, the 2-neighborhood of the center is everything.
2011 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], 5, false).unwrap();
2012 /// assert_eq!(g.local_scan_k_ecount(2, None, NeighborMode::All).unwrap(), vec![2.0, 3.0, 4.0, 3.0, 2.0]);
2013 /// ```
2014 pub fn local_scan_k_ecount(
2015 &self,
2016 k: usize,
2017 weights: Option<&[f64]>,
2018 mode: NeighborMode,
2019 ) -> Result<Vec<f64>> {
2020 let k = igraph_int_t::try_from(k).map_err(|_| Error::invalid("k is too large"))?;
2021 let w = weights_view(self, weights)?;
2022 let mut res = Vector::new();
2023 igraph_call!(igraph_local_scan_k_ecount(
2024 self,
2025 k,
2026 &mut res,
2027 ptr_of(&w),
2028 mode.into()
2029 ))?;
2030 Ok(res.into())
2031 }
2032
2033 /// "Them" local scan statistic for `k`-neighborhoods
2034 /// (`igraph_local_scan_k_ecount_them`).
2035 ///
2036 /// For every vertex, the number of edges of `them` (or the sum of their
2037 /// `weights_them`) inside the `k`-neighborhood of the vertex computed in
2038 /// `self`. The graphs must have the same vertices and directedness.
2039 ///
2040 /// Binds [`igraph_local_scan_k_ecount_them`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_k_ecount_them).
2041 ///
2042 /// # Errors
2043 ///
2044 /// As for [`local_scan_0_them`](Self::local_scan_0_them).
2045 pub fn local_scan_k_ecount_them(
2046 &self,
2047 them: &Graph,
2048 k: usize,
2049 weights_them: Option<&[f64]>,
2050 mode: NeighborMode,
2051 ) -> Result<Vec<f64>> {
2052 check_them(self, them)?;
2053 let k = igraph_int_t::try_from(k).map_err(|_| Error::invalid("k is too large"))?;
2054 let w = weights_view(them, weights_them)?;
2055 let mut res = Vector::new();
2056 self.with_fresh_multi_cache(|| {
2057 igraph_call!(igraph_local_scan_k_ecount_them(
2058 self,
2059 them,
2060 k,
2061 &mut res,
2062 ptr_of(&w),
2063 mode.into()
2064 ))
2065 })?;
2066 Ok(res.into())
2067 }
2068
2069 /// Edge counts in the subgraphs induced by arbitrary vertex subsets
2070 /// (`igraph_local_scan_subset_ecount`).
2071 ///
2072 /// Returns, for each subset, the number of edges (or the sum of their
2073 /// weights) of the subgraph it induces. Multi-edges and self-loops count
2074 /// (a loop counts once). Each subset should be a *set*: igraph does not
2075 /// deduplicate, so a repeated vertex makes its incident edges count more
2076 /// than once. Without weights, the count for a duplicate-free subset
2077 /// equals the edge count of the corresponding
2078 /// [`induced_subgraph`](Self::induced_subgraph). Time complexity:
2079 /// O(Σ_S Σ_{v ∈ S} deg(v)).
2080 ///
2081 /// Binds [`igraph_local_scan_subset_ecount`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_subset_ecount).
2082 ///
2083 /// # Errors
2084 ///
2085 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for an
2086 /// invalid vertex in a subset (igraph reports it as `IGRAPH_EINVAL`, not as
2087 /// an invalid vertex id) or for weights of the wrong length.
2088 ///
2089 /// # Examples
2090 ///
2091 /// ```
2092 /// use igraph::prelude::*;
2093 ///
2094 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], 4, false).unwrap();
2095 /// let counts = g.local_scan_subset_ecount(None, &[vec![0, 1, 2], vec![2, 3], vec![]]).unwrap();
2096 /// assert_eq!(counts, vec![3.0, 1.0, 0.0]);
2097 /// ```
2098 pub fn local_scan_subset_ecount<S: AsRef<[VertexId]>>(
2099 &self,
2100 weights: Option<&[f64]>,
2101 subsets: &[S],
2102 ) -> Result<Vec<f64>> {
2103 let w = weights_view(self, weights)?;
2104 let list: VectorIntList = subsets.iter().map(|s| s.as_ref()).collect();
2105 let mut res = Vector::new();
2106 igraph_call!(igraph_local_scan_subset_ecount(
2107 self,
2108 &mut res,
2109 ptr_of(&w),
2110 &list
2111 ))?;
2112 Ok(res.into())
2113 }
2114
2115 /// Edge counts in precomputed neighborhoods, one per vertex
2116 /// (`igraph_local_scan_neighborhood_ecount`).
2117 ///
2118 /// Like [`local_scan_subset_ecount`](Self::local_scan_subset_ecount), but
2119 /// `neighborhoods` must contain exactly one vertex set per vertex of the
2120 /// graph. The C documentation marks this function as deprecated in favor
2121 /// of `igraph_local_scan_subset_ecount` since igraph 0.10, hence the
2122 /// Rust `#[deprecated]` attribute.
2123 ///
2124 /// Binds [`igraph_local_scan_neighborhood_ecount`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_local_scan_neighborhood_ecount).
2125 ///
2126 /// # Errors
2127 ///
2128 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
2129 /// number of neighborhoods differs from the number of vertices, or as for
2130 /// [`local_scan_subset_ecount`](Self::local_scan_subset_ecount).
2131 #[deprecated(
2132 since = "1.0.1",
2133 note = "deprecated in igraph 0.10: use `local_scan_subset_ecount`"
2134 )]
2135 pub fn local_scan_neighborhood_ecount<S: AsRef<[VertexId]>>(
2136 &self,
2137 weights: Option<&[f64]>,
2138 neighborhoods: &[S],
2139 ) -> Result<Vec<f64>> {
2140 let w = weights_view(self, weights)?;
2141 let list: VectorIntList = neighborhoods.iter().map(|s| s.as_ref()).collect();
2142 let mut res = Vector::new();
2143 igraph_call!(igraph_local_scan_neighborhood_ecount(
2144 self,
2145 &mut res,
2146 ptr_of(&w),
2147 &list
2148 ))?;
2149 Ok(res.into())
2150 }
2151}