igraph/linalg/eigen.rs
1//! Generic eigensolvers (`igraph_eigen.h`).
2
3use super::{
4 ArpackOptions, Complex, SparseMat,
5 arpack::{matvec_trampoline, with_finite_guard},
6 check_finite,
7 lapack::DgeevxBalance,
8 to_c_int,
9};
10use crate::{
11 error::{Error, Result},
12 ffi::*,
13 igraph_call,
14 matrix::{Matrix, MatrixComplex},
15 vector::{Vector, VectorComplex},
16};
17use std::{ffi::c_void, ops::Range, ptr};
18
19crate::ffi_enum! {
20 /// Which numerical library [`eigen_matrix_symmetric`], [`eigen_matrix`]
21 /// and [`Graph::eigen_adjacency`](crate::Graph::eigen_adjacency) use (`igraph_eigen_algorithm_t`).
22 pub enum EigenAlgorithm: igraph_eigen_algorithm_t {
23 /// Choose automatically. For symmetric matrices: LAPACK when the matrix
24 /// is small (`n < 100`), when as many eigenvalues as the order are
25 /// requested, and for [`EigenWhich::All`], [`EigenWhich::Interval`]
26 /// and [`EigenWhich::Select`] (which only LAPACK supports); ARPACK
27 /// otherwise. ARPACK for [`Graph::eigen_adjacency`](crate::Graph::eigen_adjacency).
28 /// Not implemented by igraph (1.0.0 and 1.0.1) for [`eigen_matrix`].
29 Auto = igraph_eigen_algorithm_t_IGRAPH_EIGEN_AUTO,
30 /// Dense LAPACK routines (the matrix is formed explicitly).
31 Lapack = igraph_eigen_algorithm_t_IGRAPH_EIGEN_LAPACK,
32 /// ARPACK (matrix-free, only matrix-vector products).
33 Arpack = igraph_eigen_algorithm_t_IGRAPH_EIGEN_ARPACK,
34 /// Complex variant of `Auto` (not implemented by igraph 1.0.0 and 1.0.1).
35 CompAuto = igraph_eigen_algorithm_t_IGRAPH_EIGEN_COMP_AUTO,
36 /// Complex variant of `Lapack` (not implemented by igraph 1.0.0 and 1.0.1).
37 CompLapack = igraph_eigen_algorithm_t_IGRAPH_EIGEN_COMP_LAPACK,
38 /// Complex variant of `Arpack` (not implemented by igraph 1.0.0 and 1.0.1).
39 CompArpack = igraph_eigen_algorithm_t_IGRAPH_EIGEN_COMP_ARPACK,
40 }
41}
42
43/// Which eigenvalues to compute (`igraph_eigen_which_t`).
44///
45/// Counts must be between 1 and the order of the matrix. Not every choice is
46/// supported by every solver: the symmetric solvers accept the magnitude,
47/// algebraic, [`BothEnds`](Self::BothEnds), [`All`](Self::All),
48/// [`Interval`](Self::Interval) and [`Select`](Self::Select) choices (the
49/// last three only with LAPACK); the non-symmetric solver accepts the
50/// magnitude, real part, imaginary part, [`All`](Self::All) and
51/// [`Select`](Self::Select) choices.
52#[derive(Debug, Clone, PartialEq)]
53pub enum EigenWhich {
54 /// The given number of eigenvalues of largest magnitude.
55 LargestMagnitude(usize),
56 /// The given number of eigenvalues of smallest magnitude.
57 SmallestMagnitude(usize),
58 /// The given number of largest (algebraic) eigenvalues; symmetric only.
59 LargestAlgebraic(usize),
60 /// The given number of smallest (algebraic) eigenvalues; symmetric only.
61 SmallestAlgebraic(usize),
62 /// The given number of eigenvalues, alternating from the two ends of the
63 /// spectrum (largest first); symmetric only. LAPACK needs at least 2.
64 BothEnds(usize),
65 /// The given number of eigenvalues with largest real part; non-symmetric only.
66 LargestReal(usize),
67 /// The given number of eigenvalues with smallest real part; non-symmetric only.
68 SmallestReal(usize),
69 /// The given number of eigenvalues with largest imaginary part; non-symmetric only.
70 LargestImaginary(usize),
71 /// The given number of eigenvalues with smallest imaginary part; non-symmetric only.
72 SmallestImaginary(usize),
73 /// All the eigenvalues.
74 All,
75 /// All the eigenvalues in the half-open interval `(low, high]`;
76 /// symmetric LAPACK only.
77 Interval {
78 /// Exclusive lower bound.
79 low: f64,
80 /// Inclusive upper bound.
81 high: f64,
82 },
83 /// Eigenvalues by (zero-based) position: for symmetric matrices in
84 /// increasing algebraic order, for non-symmetric ones in increasing
85 /// magnitude. LAPACK only.
86 Select(Range<usize>),
87}
88
89impl EigenWhich {
90 pub(crate) fn to_raw(&self, n: usize) -> Result<igraph_eigen_which_t> {
91 use EigenWhich::*;
92 let mut raw = igraph_eigen_which_t {
93 pos: igraph_eigen_which_position_t_IGRAPH_EIGEN_ALL,
94 howmany: 0,
95 il: 0,
96 iu: 0,
97 vl: 0.0,
98 vu: 0.0,
99 vestimate: 0,
100 balance: DgeevxBalance::None.into(),
101 };
102 let count = |k: usize| -> Result<std::ffi::c_int> {
103 if k == 0 || k > n {
104 return Err(Error::invalid(format!(
105 "cannot compute {k} eigenvalues of a matrix of order {n}"
106 )));
107 }
108 to_c_int(k, "number of eigenvalues")
109 };
110 let (pos, howmany) = match self {
111 LargestMagnitude(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_LM, count(*k)?),
112 SmallestMagnitude(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_SM, count(*k)?),
113 LargestAlgebraic(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_LA, count(*k)?),
114 SmallestAlgebraic(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_SA, count(*k)?),
115 BothEnds(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_BE, count(*k)?),
116 LargestReal(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_LR, count(*k)?),
117 SmallestReal(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_SR, count(*k)?),
118 LargestImaginary(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_LI, count(*k)?),
119 SmallestImaginary(k) => (igraph_eigen_which_position_t_IGRAPH_EIGEN_SI, count(*k)?),
120 All => (igraph_eigen_which_position_t_IGRAPH_EIGEN_ALL, 0),
121 Interval { low, high } => {
122 if low.is_nan() || high.is_nan() || low >= high {
123 return Err(Error::invalid(
124 "the eigenvalue interval must satisfy low < high",
125 ));
126 }
127 raw.vl = *low;
128 raw.vu = *high;
129 // An upper bound on the number of eigenvalues found: always correct.
130 raw.vestimate = to_c_int(n, "matrix order")?;
131 (igraph_eigen_which_position_t_IGRAPH_EIGEN_INTERVAL, 0)
132 }
133 Select(r) => {
134 if r.start >= r.end || r.end > n {
135 return Err(Error::invalid(format!(
136 "invalid eigenvalue range {r:?} for order {n}"
137 )));
138 }
139 raw.il = to_c_int(r.start + 1, "range start")?;
140 raw.iu = to_c_int(r.end, "range end")?;
141 (igraph_eigen_which_position_t_IGRAPH_EIGEN_SELECT, 0)
142 }
143 };
144 raw.pos = pos;
145 raw.howmany = howmany;
146 Ok(raw)
147 }
148}
149
150/// Eigenvalues and eigenvectors of a real symmetric matrix.
151#[derive(Debug, Clone, PartialEq)]
152pub struct SymmetricEigen {
153 /// The eigenvalues, in the order implied by the [`EigenWhich`] choice:
154 /// decreasing magnitude for `LargestMagnitude`, increasing magnitude for
155 /// `SmallestMagnitude`, decreasing for `LargestAlgebraic`, increasing for
156 /// `SmallestAlgebraic`, `All`, `Interval` and `Select`, and alternating
157 /// largest / smallest for `BothEnds` — whichever algorithm is used.
158 pub values: Vec<f64>,
159 /// The unit eigenvectors, in the columns (`n` × number of eigenvalues).
160 pub vectors: Matrix,
161}
162
163/// Eigenvalues and eigenvectors of a general real matrix.
164#[derive(Debug, Clone, PartialEq)]
165pub struct ComplexEigen {
166 /// The (possibly complex) eigenvalues.
167 pub values: Vec<Complex>,
168 /// The eigenvectors: `vectors[k]` belongs to `values[k]`.
169 pub vectors: Vec<Vec<Complex>>,
170}
171
172enum Operator<'a> {
173 Dense(&'a Matrix),
174 Sparse(&'a SparseMat),
175 Fn(igraph_arpack_function_t, *mut c_void),
176}
177
178fn symmetric_common(
179 op: Operator<'_>,
180 n: usize,
181 which: &EigenWhich,
182 algorithm: EigenAlgorithm,
183 options: &ArpackOptions,
184) -> Result<SymmetricEigen> {
185 if n == 0 {
186 return Err(Error::invalid(
187 "cannot compute the eigenvalues of an empty matrix",
188 ));
189 }
190 let cn = to_c_int(n, "matrix order")?;
191 // Resolve `Auto` here: igraph (1.0.0 and 1.0.1) compares the (unused,
192 // zero) `howmany` of `All`, `Interval` and `Select` with `n` and so picks
193 // ARPACK for them when `n >= 100`, where they fail. And for `n <= 2`
194 // igraph's "ARPACK" code path is a closed-form shortcut that confuses
195 // magnitude with algebraic order (see `uses_2x2_shortcut`): LAPACK is
196 // exact there.
197 let algorithm = match (algorithm, which) {
198 (
199 EigenAlgorithm::Auto,
200 EigenWhich::All | EigenWhich::Interval { .. } | EigenWhich::Select(_),
201 ) => EigenAlgorithm::Lapack,
202 (
203 EigenAlgorithm::Auto,
204 EigenWhich::LargestMagnitude(k)
205 | EigenWhich::SmallestMagnitude(k)
206 | EigenWhich::LargestAlgebraic(k)
207 | EigenWhich::SmallestAlgebraic(k)
208 | EigenWhich::BothEnds(k),
209 ) if *k == n || n < 100 => EigenAlgorithm::Lapack,
210 (EigenAlgorithm::Auto, _) => EigenAlgorithm::Arpack,
211 (EigenAlgorithm::Arpack, _) if n <= 2 => EigenAlgorithm::Lapack,
212 (other, _) => other,
213 };
214 // igraph's (1.0.0 and 1.0.1) LAPACK code path for "smallest magnitude" walks outside the
215 // eigenvalue array (and the eigenvector matrix) whenever the eigenvalue
216 // of smallest magnitude is at either end of the spectrum, e.g. for every
217 // positive definite matrix. Compute the whole spectrum with LAPACK
218 // instead and pick the eigenpairs here.
219 let sm_with_lapack = match which {
220 EigenWhich::SmallestMagnitude(_) => {
221 which.to_raw(n)?;
222 algorithm == EigenAlgorithm::Lapack
223 }
224 _ => false,
225 };
226 let raw_which = if sm_with_lapack {
227 EigenWhich::All.to_raw(n)?
228 } else {
229 which.to_raw(n)?
230 };
231 let mut raw_opts = options.to_raw_unchecked_which(n)?;
232 let (a, sa, fun, extra) = match op {
233 Operator::Dense(m) => (m as *const Matrix, ptr::null(), None, ptr::null_mut()),
234 Operator::Sparse(s) => (ptr::null(), s as *const SparseMat, None, ptr::null_mut()),
235 Operator::Fn(f, e) => (ptr::null(), ptr::null(), f, e),
236 };
237 let mut values = Vector::new();
238 let mut vectors = Matrix::new();
239 // Only the ARPACK code path uses ARPACK's non-re-entrant state.
240 let _arpack = if algorithm == EigenAlgorithm::Lapack {
241 None
242 } else {
243 Some(super::arpack::ArpackGuard::enter()?)
244 };
245 igraph_call!(igraph_eigen_matrix_symmetric(
246 a,
247 sa,
248 fun,
249 cn,
250 extra,
251 algorithm.into(),
252 &raw_which,
253 &mut raw_opts,
254 ptr::null_mut(),
255 &mut values,
256 &mut vectors
257 ))?;
258 SymmetricEigen {
259 values: values.into(),
260 vectors,
261 }
262 .ordered(which)
263}
264
265impl SymmetricEigen {
266 /// Puts the eigenpairs in the documented order for `which` (igraph's
267 /// LAPACK and ARPACK code paths do not agree on it), keeping only the
268 /// requested number of them.
269 fn ordered(self, which: &EigenWhich) -> Result<Self> {
270 let key: fn(f64, f64) -> std::cmp::Ordering = match which {
271 EigenWhich::LargestMagnitude(_) => |a, b| b.abs().total_cmp(&a.abs()),
272 EigenWhich::SmallestMagnitude(_) => |a, b| a.abs().total_cmp(&b.abs()),
273 EigenWhich::LargestAlgebraic(_) => |a, b| b.total_cmp(&a),
274 EigenWhich::SmallestAlgebraic(_) => |a, b| a.total_cmp(&b),
275 _ => return Ok(self),
276 };
277 let k = match which {
278 EigenWhich::LargestMagnitude(k)
279 | EigenWhich::SmallestMagnitude(k)
280 | EigenWhich::LargestAlgebraic(k)
281 | EigenWhich::SmallestAlgebraic(k) => (*k).min(self.values.len()),
282 _ => self.values.len(),
283 };
284 let mut order: Vec<usize> = (0..self.values.len()).collect();
285 // Stable: ties keep igraph's order.
286 order.sort_by(|&i, &j| key(self.values[i], self.values[j]));
287 order.truncate(k);
288 let n = self.vectors.nrow();
289 let has_vectors = self.vectors.ncol() == self.values.len();
290 let mut data = Vec::with_capacity(n * k);
291 let mut values = Vec::with_capacity(k);
292 for &i in &order {
293 values.push(self.values[i]);
294 if has_vectors {
295 let v = self.vectors.column(i);
296 let norm = v.iter().map(|x| x * x).sum::<f64>().sqrt();
297 if n <= 2 && norm > 0.0 {
298 // igraph's closed-form 2 x 2 "ARPACK" shortcut does not
299 // normalize the eigenvectors.
300 data.extend(v.iter().map(|x| x / norm));
301 } else {
302 data.extend_from_slice(v);
303 }
304 }
305 }
306 let vectors = if has_vectors {
307 Matrix::from_column_major(n, k, &data)?
308 } else {
309 self.vectors
310 };
311 Ok(Self { values, vectors })
312 }
313}
314
315/// Selected eigenvalues and eigenvectors of a real **symmetric** dense
316/// matrix (`igraph_eigen_matrix_symmetric`), with LAPACK or ARPACK.
317///
318/// Only the upper triangle is used by LAPACK. `options` tunes ARPACK: only
319/// its `tol` and `mxiter` are used (`which`, `nev` and `ncv` are derived
320/// from `which`, and ARPACK starts from a random vector of the calling
321/// thread's RNG). Matrices of order at most 2 always use LAPACK.
322///
323/// Binds `igraph_eigen_matrix_symmetric` (see the
324/// [linear algebra chapter](https://igraph.org/c/html/latest/igraph-Linalg.html)).
325///
326/// # Errors
327/// If the matrix is not square or empty, the choice is invalid for the
328/// matrix order, or not supported by the algorithm (e.g. `Select` with
329/// ARPACK gives [`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented)).
330///
331/// # Examples
332///
333/// ```
334/// use igraph::{linalg::*, prelude::*};
335/// // Eigenvalues of [[2, 1], [1, 2]] are 1 and 3.
336/// let a = Matrix::from_rows(&[[2.0, 1.0], [1.0, 2.0]]).unwrap();
337/// let e = eigen_matrix_symmetric(&a, &EigenWhich::All, EigenAlgorithm::Lapack, &ArpackOptions::default()).unwrap();
338/// let mut values = e.values.clone();
339/// values.sort_by(f64::total_cmp);
340/// assert!((values[0] - 1.0).abs() < 1e-12 && (values[1] - 3.0).abs() < 1e-12);
341/// ```
342pub fn eigen_matrix_symmetric(
343 a: &Matrix,
344 which: &EigenWhich,
345 algorithm: EigenAlgorithm,
346 options: &ArpackOptions,
347) -> Result<SymmetricEigen> {
348 if a.nrow() != a.ncol() {
349 return Err(Error::invalid("eigenvalues need a square matrix"));
350 }
351 check_finite(a.as_slice(), "the matrix")?;
352 symmetric_common(Operator::Dense(a), a.nrow(), which, algorithm, options)
353}
354
355/// Selected eigenvalues and eigenvectors of a real **symmetric** operator
356/// given by a matrix-vector product closure (`igraph_eigen_matrix_symmetric`
357/// with a callback), see [`arpack_rssolve`](super::arpack_rssolve) for the
358/// closure contract. With LAPACK the matrix is first formed by applying the
359/// closure to the unit vectors.
360///
361/// Binds `igraph_eigen_matrix_symmetric` (see the
362/// [linear algebra chapter](https://igraph.org/c/html/latest/igraph-Linalg.html)).
363///
364/// # Errors
365/// As [`eigen_matrix_symmetric`], plus non-finite values produced by the
366/// closure.
367///
368/// # Examples
369///
370/// The operator `x -> (sum x) 1` (the all-ones matrix `J_4`) has eigenvalue
371/// `4` once and `0` three times.
372///
373/// ```
374/// use igraph::linalg::*;
375/// let ones = |x: &[f64], y: &mut [f64]| {
376/// let s: f64 = x.iter().sum();
377/// y.iter_mut().for_each(|yi| *yi = s);
378/// };
379/// let e = eigen_symmetric_fn(4, ones, &EigenWhich::All, EigenAlgorithm::Lapack, &ArpackOptions::default())
380/// .unwrap();
381/// let mut values = e.values.clone();
382/// values.sort_by(f64::total_cmp);
383/// assert!(values[..3].iter().all(|x| x.abs() < 1e-12) && (values[3] - 4.0).abs() < 1e-12);
384/// ```
385pub fn eigen_symmetric_fn<F: FnMut(&[f64], &mut [f64])>(
386 n: usize,
387 matvec: F,
388 which: &EigenWhich,
389 algorithm: EigenAlgorithm,
390 options: &ArpackOptions,
391) -> Result<SymmetricEigen> {
392 with_finite_guard(matvec, |mut f| {
393 let op = Operator::Fn(
394 Some(matvec_trampoline::<&mut dyn FnMut(&[f64], &mut [f64])>),
395 &mut f as *mut &mut dyn FnMut(&[f64], &mut [f64]) as *mut c_void,
396 );
397 symmetric_common(op, n, which, algorithm, options)
398 })
399}
400
401fn complex_common(
402 op: Operator<'_>,
403 n: usize,
404 which: &EigenWhich,
405 algorithm: EigenAlgorithm,
406) -> Result<ComplexEigen> {
407 if n == 0 {
408 return Err(Error::invalid(
409 "cannot compute the eigenvalues of an empty matrix",
410 ));
411 }
412 let cn = to_c_int(n, "matrix order")?;
413 let raw_which = which.to_raw(n)?;
414 let (a, sa) = match op {
415 Operator::Dense(m) => (m as *const Matrix, ptr::null()),
416 Operator::Sparse(s) => (ptr::null(), s as *const SparseMat),
417 Operator::Fn(..) => unreachable!("closures are not supported by igraph_eigen_matrix"),
418 };
419 let mut raw_opts = ArpackOptions::default().to_raw_unchecked_which(n)?;
420 let mut values = VectorComplex::new();
421 let mut vectors = MatrixComplex::new();
422 igraph_call!(igraph_eigen_matrix(
423 a,
424 sa,
425 None,
426 cn,
427 ptr::null_mut(),
428 algorithm.into(),
429 &raw_which,
430 &mut raw_opts,
431 ptr::null_mut(),
432 &mut values,
433 &mut vectors
434 ))?;
435 Ok(ComplexEigen {
436 values: values.to_vec(),
437 vectors: vectors.columns().map(<[Complex]>::to_vec).collect(),
438 })
439}
440
441/// Selected eigenvalues and eigenvectors of a general real dense matrix
442/// (`igraph_eigen_matrix`); only [`EigenAlgorithm::Lapack`] is implemented
443/// by igraph 1.0.0 and 1.0.1.
444///
445/// The eigenvalues are ordered according to `which`; [`EigenWhich::All`]
446/// and [`EigenWhich::Select`] use increasing magnitude. Among eigenvalues of
447/// equal magnitude (resp. real or imaginary part), real ones come first for
448/// the "largest" choices and complex ones come first for the "smallest"
449/// choices, `All` and `Select`, so that each "smallest" order is the exact
450/// reverse of the corresponding "largest" one.
451///
452/// Binds `igraph_eigen_matrix` (see the
453/// [linear algebra chapter](https://igraph.org/c/html/latest/igraph-Linalg.html)).
454/// The callback form of the C function is not exposed: igraph 1.0.0 and 1.0.1
455/// dereferences a null matrix in that code path.
456///
457/// # Examples
458///
459/// A rotation by 90 degrees has eigenvalues `±i`.
460///
461/// ```
462/// use igraph::{linalg::*, prelude::*};
463/// let r = Matrix::from_rows(&[[0.0, -1.0], [1.0, 0.0]]).unwrap();
464/// let e = eigen_matrix(&r, &EigenWhich::All, EigenAlgorithm::Lapack).unwrap();
465/// assert_eq!(e.values.len(), 2);
466/// for v in &e.values {
467/// assert!(v.re().abs() < 1e-12 && (v.im().abs() - 1.0).abs() < 1e-12);
468/// }
469/// ```
470pub fn eigen_matrix(
471 a: &Matrix,
472 which: &EigenWhich,
473 algorithm: EigenAlgorithm,
474) -> Result<ComplexEigen> {
475 if a.nrow() != a.ncol() {
476 return Err(Error::invalid("eigenvalues need a square matrix"));
477 }
478 check_finite(a.as_slice(), "the matrix")?;
479 complex_common(Operator::Dense(a), a.nrow(), which, algorithm)
480}
481
482impl igraph_sparsemat_t {
483 /// Selected eigenvalues and eigenvectors of a symmetric sparse matrix
484 /// (`igraph_eigen_matrix_symmetric` with a sparse input); see
485 /// [`eigen_matrix_symmetric`].
486 pub fn eigen_symmetric(
487 &self,
488 which: &EigenWhich,
489 algorithm: EigenAlgorithm,
490 options: &ArpackOptions,
491 ) -> Result<SymmetricEigen> {
492 if self.nrow() != self.ncol() {
493 return Err(Error::invalid("eigenvalues need a square matrix"));
494 }
495 let cc = self.compress()?;
496 check_finite(&cc.getelements()?.x, "the matrix")?;
497 symmetric_common(
498 Operator::Sparse(&cc),
499 self.nrow(),
500 which,
501 algorithm,
502 options,
503 )
504 }
505
506 /// Selected eigenvalues and eigenvectors of a general sparse matrix
507 /// (`igraph_eigen_matrix` with a sparse input); see [`eigen_matrix`].
508 pub fn eigen(&self, which: &EigenWhich, algorithm: EigenAlgorithm) -> Result<ComplexEigen> {
509 if self.nrow() != self.ncol() {
510 return Err(Error::invalid("eigenvalues need a square matrix"));
511 }
512 let cc = self.compress()?;
513 check_finite(&cc.getelements()?.x, "the matrix")?;
514 complex_common(Operator::Sparse(&cc), self.nrow(), which, algorithm)
515 }
516}
517
518impl igraph_t {
519 /// Eigenvalues and eigenvectors of the adjacency matrix of an undirected
520 /// graph (`igraph_eigen_adjacency`), computed with ARPACK (the only
521 /// algorithm implemented by igraph 1.0.0 and 1.0.1, also chosen by
522 /// [`EigenAlgorithm::Auto`]). Self-loops count once on the diagonal,
523 /// multi-edges add up.
524 ///
525 /// Supported choices: largest/smallest magnitude, largest/smallest
526 /// algebraic.
527 ///
528 /// Binds `igraph_eigen_adjacency` (see the
529 /// [linear algebra chapter](https://igraph.org/c/html/latest/igraph-Linalg.html)).
530 ///
531 /// ARPACK starts from a random vector drawn from the calling thread's
532 /// igraph RNG ([`rng::seed`](crate::rng::seed) makes runs reproducible).
533 /// On small graphs whose wanted eigenvalue is highly repeated (e.g. the
534 /// eigenvalue `-1` of a complete graph) it can stop with "maximum number
535 /// of iterations reached" for some start vectors; LAPACK on the dense
536 /// matrix is the robust choice there.
537 ///
538 /// See also [`Graph::eigenvector_centrality`](crate::Graph::eigenvector_centrality)
539 /// (the scaled leading eigenvector, also for directed and weighted
540 /// graphs) and [`Graph::get_adjacency`](crate::Graph::get_adjacency) to
541 /// form the dense matrix for [`eigen_matrix_symmetric`] or
542 /// [`lapack_dsyevr`](super::lapack_dsyevr).
543 ///
544 /// # Errors
545 /// For directed graphs and unsupported choices
546 /// ([`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented)).
547 ///
548 /// # Examples
549 ///
550 /// The complete graph `K_n` has adjacency eigenvalues `n - 1` (once) and
551 /// `-1`.
552 ///
553 /// ```
554 /// use igraph::{linalg::*, prelude::*};
555 /// let k6 = Graph::full(6, false, false).unwrap();
556 /// let opts = ArpackOptions::default();
557 /// let e = k6.eigen_adjacency(&EigenWhich::LargestAlgebraic(1), EigenAlgorithm::Auto, &opts).unwrap();
558 /// assert!((e.values[0] - 5.0).abs() < 1e-10);
559 /// ```
560 pub fn eigen_adjacency(
561 &self,
562 which: &EigenWhich,
563 algorithm: EigenAlgorithm,
564 options: &ArpackOptions,
565 ) -> Result<SymmetricEigen> {
566 let n = self.vcount();
567 if n == 0 {
568 return Err(Error::invalid("the graph has no vertices"));
569 }
570 let raw_which = which.to_raw(n)?;
571 let mut raw_opts = options.to_raw_unchecked_which(n)?;
572 let mut values = Vector::new();
573 let mut vectors = Matrix::new();
574 let mut cvalues = VectorComplex::new();
575 let mut cvectors = MatrixComplex::new();
576 let _arpack = super::arpack::ArpackGuard::enter()?;
577 igraph_call!(igraph_eigen_adjacency(
578 self,
579 algorithm.into(),
580 &raw_which,
581 &mut raw_opts,
582 ptr::null_mut(),
583 &mut values,
584 &mut vectors,
585 &mut cvalues,
586 &mut cvectors
587 ))?;
588 SymmetricEigen {
589 values: values.into(),
590 vectors,
591 }
592 .ordered(which)
593 }
594}