advanced_algorithms/numerical/
svd.rs1use crate::{AlgorithmError, Result};
30use crate::numerical::qr_decomposition::QRDecomposition;
31
32pub struct SVD {
34 u: Vec<Vec<f64>>,
35 singular_values: Vec<f64>,
36 vt: Vec<Vec<f64>>,
37}
38
39impl SVD {
40 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 if m < n {
69 let transposed = transpose(matrix);
70 let svd = Self::decompose_internal(&transposed, tolerance, max_iterations)?;
71
72 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 let at = transpose(matrix);
93 let ata = matrix_multiply(&at, matrix)?;
94
95 let (eigenvalues, eigenvectors) = qr_algorithm(&ata, tolerance, max_iterations)?;
97
98 let singular_values: Vec<f64> = eigenvalues.iter()
100 .map(|&x| if x > 0.0 { x.sqrt() } else { 0.0 })
101 .collect();
102
103 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 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 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 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 pub fn u(&self) -> &[Vec<f64>] {
149 &self.u
150 }
151
152 pub fn singular_values(&self) -> &[f64] {
154 &self.singular_values
155 }
156
157 pub fn vt(&self) -> &[Vec<f64>] {
159 &self.vt
160 }
161
162 pub fn rank(&self, tolerance: f64) -> usize {
164 self.singular_values.iter()
165 .filter(|&&s| s > tolerance)
166 .count()
167 }
168
169 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
186fn 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 a = matrix_multiply(&r, &q)?;
203
204 q_total = matrix_multiply(&q_total, &q)?;
206
207 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 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 for i in 0..m {
281 for j in 0..n {
282 extended[i][j] = u[i][j];
283 }
284 }
285
286 #[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 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 assert_eq!(rank, 1);
327 }
328}