advanced_algorithms/numerical/
svd.rs

1//! Singular Value Decomposition (SVD)
2//!
3//! SVD factorizes a matrix A into the product U * Σ * V^T, where:
4//! - U is an orthogonal matrix (left singular vectors)
5//! - Σ is a diagonal matrix (singular values)
6//! - V^T is an orthogonal matrix (right singular vectors)
7//!
8//! SVD is used for:
9//! - Matrix approximation and compression
10//! - Principal Component Analysis (PCA)
11//! - Solving linear least squares problems
12//! - Pseudoinverse computation
13//!
14//! # Examples
15//!
16//! ```
17//! use advanced_algorithms::numerical::svd::SVD;
18//!
19//! let matrix = vec![
20//!     vec![1.0, 2.0],
21//!     vec![3.0, 4.0],
22//!     vec![5.0, 6.0],
23//! ];
24//!
25//! let svd = SVD::decompose(&matrix, 1e-10, 1000).unwrap();
26//! let singular_values = svd.singular_values();
27//! ```
28
29use crate::{AlgorithmError, Result};
30use crate::numerical::qr_decomposition::QRDecomposition;
31
32/// SVD decomposition result
33pub struct SVD {
34    u: Vec<Vec<f64>>,
35    singular_values: Vec<f64>,
36    vt: Vec<Vec<f64>>,
37}
38
39impl SVD {
40    /// Performs Singular Value Decomposition
41    ///
42    /// Uses the Golub-Reinsch algorithm for SVD computation
43    ///
44    /// # Arguments
45    ///
46    /// * `matrix` - An m×n matrix
47    /// * `tolerance` - Convergence tolerance
48    /// * `max_iterations` - Maximum iterations for convergence
49    ///
50    /// # Returns
51    ///
52    /// SVD decomposition containing U, Σ, and V^T
53    pub fn decompose(
54        matrix: &[Vec<f64>],
55        tolerance: f64,
56        max_iterations: usize,
57    ) -> Result<Self> {
58        if matrix.is_empty() || matrix[0].is_empty() {
59            return Err(AlgorithmError::InvalidInput(
60                "Matrix cannot be empty".to_string()
61            ));
62        }
63        
64        let m = matrix.len();
65        let n = matrix[0].len();
66        
67        // For numerical stability, if m < n, work with transpose
68        if m < n {
69            let transposed = transpose(matrix);
70            let svd = Self::decompose_internal(&transposed, tolerance, max_iterations)?;
71            
72            // Swap U and V^T
73            return Ok(SVD {
74                u: svd.vt,
75                singular_values: svd.singular_values,
76                vt: svd.u,
77            });
78        }
79        
80        Self::decompose_internal(matrix, tolerance, max_iterations)
81    }
82    
83    fn decompose_internal(
84        matrix: &[Vec<f64>],
85        tolerance: f64,
86        max_iterations: usize,
87    ) -> Result<Self> {
88        let m = matrix.len();
89        let n = matrix[0].len();
90        
91        // Step 1: Compute A^T * A
92        let at = transpose(matrix);
93        let ata = matrix_multiply(&at, matrix)?;
94        
95        // Step 2: Find eigenvalues and eigenvectors of A^T * A using QR algorithm
96        let (eigenvalues, eigenvectors) = qr_algorithm(&ata, tolerance, max_iterations)?;
97        
98        // Step 3: Singular values are square roots of eigenvalues
99        let singular_values: Vec<f64> = eigenvalues.iter()
100            .map(|&x| if x > 0.0 { x.sqrt() } else { 0.0 })
101            .collect();
102        
103        // Sort singular values in descending order
104        let mut indices: Vec<usize> = (0..n).collect();
105        indices.sort_by(|&i, &j| {
106            singular_values[j].partial_cmp(&singular_values[i]).unwrap()
107        });
108        
109        let sorted_values: Vec<f64> = indices.iter()
110            .map(|&i| singular_values[i])
111            .collect();
112        
113        // Step 4: V is the matrix of eigenvectors
114        let mut v = vec![vec![0.0; n]; n];
115        for (j, &idx) in indices.iter().enumerate() {
116            for i in 0..n {
117                v[i][j] = eigenvectors[i][idx];
118            }
119        }
120        
121        let vt = transpose(&v);
122        
123        // Step 5: Compute U = A * V * Σ^(-1)
124        let av = matrix_multiply(matrix, &v)?;
125        let mut u = vec![vec![0.0; n]; m];
126        
127        for i in 0..m {
128            for j in 0..n {
129                if sorted_values[j] > tolerance {
130                    u[i][j] = av[i][j] / sorted_values[j];
131                }
132            }
133        }
134        
135        // Extend U to be m×m if needed
136        if m > n {
137            u = extend_u(&u, m);
138        }
139        
140        Ok(SVD {
141            u,
142            singular_values: sorted_values,
143            vt,
144        })
145    }
146    
147    /// Returns the U matrix (left singular vectors)
148    pub fn u(&self) -> &[Vec<f64>] {
149        &self.u
150    }
151    
152    /// Returns the singular values
153    pub fn singular_values(&self) -> &[f64] {
154        &self.singular_values
155    }
156    
157    /// Returns the V^T matrix (right singular vectors transposed)
158    pub fn vt(&self) -> &[Vec<f64>] {
159        &self.vt
160    }
161    
162    /// Computes the matrix rank
163    pub fn rank(&self, tolerance: f64) -> usize {
164        self.singular_values.iter()
165            .filter(|&&s| s > tolerance)
166            .count()
167    }
168    
169    /// Computes the condition number (ratio of largest to smallest singular value)
170    pub fn condition_number(&self) -> f64 {
171        let max_sv = self.singular_values.iter()
172            .fold(0.0f64, |a, &b| a.max(b));
173        
174        let min_sv = self.singular_values.iter()
175            .filter(|&&s| s > 1e-10)
176            .fold(f64::INFINITY, |a, &b| a.min(b));
177        
178        if min_sv > 0.0 && min_sv.is_finite() {
179            max_sv / min_sv
180        } else {
181            f64::INFINITY
182        }
183    }
184}
185
186/// QR algorithm for eigenvalue computation
187fn qr_algorithm(
188    matrix: &[Vec<f64>],
189    tolerance: f64,
190    max_iterations: usize,
191) -> Result<(Vec<f64>, Vec<Vec<f64>>)> {
192    let n = matrix.len();
193    let mut a = matrix.to_vec();
194    let mut q_total = identity_matrix(n);
195    
196    for _ in 0..max_iterations {
197        let qr = QRDecomposition::decompose(&a)?;
198        let q = qr.q().to_vec();
199        let r = qr.r().to_vec();
200        
201        // Update A = R * Q
202        a = matrix_multiply(&r, &q)?;
203        
204        // Update total Q
205        q_total = matrix_multiply(&q_total, &q)?;
206        
207        // Check for convergence (off-diagonal elements should be near zero)
208        let mut max_off_diag: f64 = 0.0;
209        #[allow(clippy::needless_range_loop)]
210        for i in 0..n {
211            for j in 0..n {
212                if i != j {
213                    max_off_diag = max_off_diag.max(a[i][j].abs());
214                }
215            }
216        }
217        
218        if max_off_diag < tolerance {
219            break;
220        }
221    }
222    
223    // Extract eigenvalues from diagonal
224    let eigenvalues: Vec<f64> = (0..n).map(|i| a[i][i]).collect();
225    
226    Ok((eigenvalues, q_total))
227}
228
229fn transpose(matrix: &[Vec<f64>]) -> Vec<Vec<f64>> {
230    let m = matrix.len();
231    let n = matrix[0].len();
232    let mut result = vec![vec![0.0; m]; n];
233    
234    for i in 0..m {
235        for j in 0..n {
236            result[j][i] = matrix[i][j];
237        }
238    }
239    
240    result
241}
242
243fn matrix_multiply(a: &[Vec<f64>], b: &[Vec<f64>]) -> Result<Vec<Vec<f64>>> {
244    let m = a.len();
245    let n = b[0].len();
246    let p = a[0].len();
247    
248    if p != b.len() {
249        return Err(AlgorithmError::DimensionMismatch(
250            "Matrix dimensions incompatible".to_string()
251        ));
252    }
253    
254    let mut result = vec![vec![0.0; n]; m];
255    
256    for i in 0..m {
257        for j in 0..n {
258            for k in 0..p {
259                result[i][j] += a[i][k] * b[k][j];
260            }
261        }
262    }
263    
264    Ok(result)
265}
266
267fn identity_matrix(n: usize) -> Vec<Vec<f64>> {
268    let mut matrix = vec![vec![0.0; n]; n];
269    for (i, row) in matrix.iter_mut().enumerate() {
270        row[i] = 1.0;
271    }
272    matrix
273}
274
275fn extend_u(u: &[Vec<f64>], m: usize) -> Vec<Vec<f64>> {
276    let n = u[0].len();
277    let mut extended = vec![vec![0.0; m]; m];
278    
279    // Copy existing U
280    for i in 0..m {
281        for j in 0..n {
282            extended[i][j] = u[i][j];
283        }
284    }
285    
286    // Fill remaining with orthogonal vectors (simple approach)
287    #[allow(clippy::needless_range_loop)]
288    for i in n..m {
289        extended[i][i] = 1.0;
290    }
291    
292    extended
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    
299    #[test]
300    fn test_svd_basic() {
301        let matrix = vec![
302            vec![1.0, 2.0],
303            vec![3.0, 4.0],
304            vec![5.0, 6.0],
305        ];
306        
307        let svd = SVD::decompose(&matrix, 1e-10, 1000).unwrap();
308        let sv = svd.singular_values();
309        
310        // Singular values should be positive and in descending order
311        assert!(sv[0] >= sv[1]);
312        assert!(sv[0] > 0.0);
313    }
314    
315    #[test]
316    fn test_svd_rank() {
317        let matrix = vec![
318            vec![1.0, 2.0],
319            vec![2.0, 4.0],
320        ];
321        
322        let svd = SVD::decompose(&matrix, 1e-10, 1000).unwrap();
323        let rank = svd.rank(1e-8);
324        
325        // This matrix has rank 1
326        assert_eq!(rank, 1);
327    }
328}