Skip to main content

trueno/eigen/
methods.rs

1//! Public API methods and iterator for `SymmetricEigen`.
2
3use super::SymmetricEigen;
4use crate::{Backend, Matrix, TruenoError, Vector};
5
6impl SymmetricEigen {
7    /// Returns the eigenvalues in descending order
8    ///
9    /// # Example
10    ///
11    /// ```
12    /// use trueno::{Matrix, SymmetricEigen};
13    ///
14    /// let m = Matrix::from_vec(2, 2, vec![3.0, 1.0, 1.0, 3.0])?;
15    /// let eigen = SymmetricEigen::new(&m)?;
16    ///
17    /// let values = eigen.eigenvalues();
18    /// assert!((values[0] - 4.0).abs() < 1e-5);  // λ₁ = 4
19    /// assert!((values[1] - 2.0).abs() < 1e-5);  // λ₂ = 2
20    /// # Ok::<(), trueno::TruenoError>(())
21    /// ```
22    pub fn eigenvalues(&self) -> &[f32] {
23        &self.eigenvalues
24    }
25
26    /// Returns the eigenvector matrix
27    ///
28    /// Columns are eigenvectors, ordered to correspond with eigenvalues.
29    /// Column `i` is the eigenvector for `eigenvalues()[i]`.
30    ///
31    /// # Example
32    ///
33    /// ```
34    /// use trueno::{Matrix, SymmetricEigen};
35    ///
36    /// let m = Matrix::identity(3);
37    /// let eigen = SymmetricEigen::new(&m)?;
38    ///
39    /// // Identity matrix has eigenvectors that are the standard basis
40    /// let vectors = eigen.eigenvectors();
41    /// assert_eq!(vectors.rows(), 3);
42    /// assert_eq!(vectors.cols(), 3);
43    /// # Ok::<(), trueno::TruenoError>(())
44    /// ```
45    pub fn eigenvectors(&self) -> &Matrix<f32> {
46        &self.eigenvectors
47    }
48
49    /// Returns an iterator over (eigenvalue, eigenvector) pairs
50    ///
51    /// # Example
52    ///
53    /// ```
54    /// use trueno::{Matrix, SymmetricEigen};
55    ///
56    /// let m = Matrix::from_vec(2, 2, vec![2.0, 0.0, 0.0, 1.0])?;
57    /// let eigen = SymmetricEigen::new(&m)?;
58    ///
59    /// for (value, vector) in eigen.iter() {
60    ///     println!("λ = {}, v = {:?}", value, vector.as_slice());
61    /// }
62    /// # Ok::<(), trueno::TruenoError>(())
63    /// ```
64    pub fn iter(&self) -> EigenIterator<'_> {
65        EigenIterator { eigen: self, index: 0 }
66    }
67
68    /// Returns the number of eigenvalue/eigenvector pairs
69    pub fn len(&self) -> usize {
70        self.eigenvalues.len()
71    }
72
73    /// Returns true if there are no eigenvalues
74    pub fn is_empty(&self) -> bool {
75        self.eigenvalues.is_empty()
76    }
77
78    /// Returns the backend used for computation
79    pub fn backend(&self) -> Backend {
80        self.backend
81    }
82
83    /// Get a specific eigenvector by index
84    ///
85    /// # Arguments
86    ///
87    /// * `i` - Index of the eigenvector (0 = largest eigenvalue)
88    ///
89    /// # Returns
90    ///
91    /// The eigenvector as a Vector, or None if index out of bounds
92    pub fn eigenvector(&self, i: usize) -> Option<Vector<f32>> {
93        contract_pre_eigenvector!();
94        if i >= self.eigenvalues.len() {
95            return None;
96        }
97
98        let n = self.eigenvectors.rows();
99        let mut data = Vec::with_capacity(n);
100
101        for row in 0..n {
102            if let Some(&val) = self.eigenvectors.get(row, i) {
103                data.push(val);
104            }
105        }
106
107        Some(Vector::from_slice(&data))
108    }
109
110    /// Reconstruct the original matrix from eigendecomposition
111    ///
112    /// Computes `V × D × V^T` where D is the diagonal matrix of eigenvalues.
113    /// This is useful for verifying the decomposition accuracy.
114    ///
115    /// # Example
116    ///
117    /// ```
118    /// use trueno::{Matrix, SymmetricEigen};
119    ///
120    /// let m = Matrix::from_vec(2, 2, vec![4.0, 2.0, 2.0, 4.0])?;
121    /// let eigen = SymmetricEigen::new(&m)?;
122    /// let reconstructed = eigen.reconstruct()?;
123    ///
124    /// // Should be approximately equal to original
125    /// assert!((reconstructed.get(0, 0).unwrap() - 4.0).abs() < 1e-5);
126    /// # Ok::<(), trueno::TruenoError>(())
127    /// ```
128    pub fn reconstruct(&self) -> Result<Matrix<f32>, TruenoError> {
129        let n = self.eigenvalues.len();
130
131        // V × D (scale each column by its eigenvalue)
132        let mut vd_data = vec![0.0f32; n * n];
133        for i in 0..n {
134            let lambda = self.eigenvalues[i];
135            for j in 0..n {
136                if let Some(&v) = self.eigenvectors.get(j, i) {
137                    vd_data[j * n + i] = v * lambda;
138                }
139            }
140        }
141
142        let vd = Matrix::from_vec(n, n, vd_data)?;
143        let vt = self.eigenvectors.transpose();
144
145        vd.matmul(&vt)
146    }
147}
148
149/// Iterator over eigenvalue-eigenvector pairs
150pub struct EigenIterator<'a> {
151    pub(super) eigen: &'a SymmetricEigen,
152    pub(super) index: usize,
153}
154
155impl<'a> Iterator for EigenIterator<'a> {
156    type Item = (f32, Vector<f32>);
157
158    fn next(&mut self) -> Option<Self::Item> {
159        if self.index >= self.eigen.len() {
160            return None;
161        }
162
163        let value = self.eigen.eigenvalues[self.index];
164        let vector = self.eigen.eigenvector(self.index)?;
165        self.index += 1;
166
167        Some((value, vector))
168    }
169
170    fn size_hint(&self) -> (usize, Option<usize>) {
171        let remaining = self.eigen.len() - self.index;
172        (remaining, Some(remaining))
173    }
174}
175
176impl<'a> ExactSizeIterator for EigenIterator<'a> {}