1pub mod legacy;
2pub mod error;
3pub(crate) mod utils;
4
5pub mod randomized;
6
7pub mod lanczos;
8
9pub use utils::*;
10
11
12#[cfg(test)]
13mod simple_comparison_tests {
14 use super::*;
15 use legacy;
16 use nalgebra_sparse::coo::CooMatrix;
17 use nalgebra_sparse::CsrMatrix;
18 use rand::{Rng, SeedableRng};
19 use rand::rngs::StdRng;
20 use rayon::ThreadPoolBuilder;
21
22 fn create_sparse_matrix(rows: usize, cols: usize, density: f64) -> nalgebra_sparse::coo::CooMatrix<f64> {
23 use rand::{rngs::StdRng, Rng, SeedableRng};
24 use std::collections::HashSet;
25
26 let mut coo = nalgebra_sparse::coo::CooMatrix::new(rows, cols);
27
28 let mut rng = StdRng::seed_from_u64(42);
29
30 let nnz = (rows as f64 * cols as f64 * density).round() as usize;
31
32 let nnz = nnz.max(1);
33
34 let mut positions = HashSet::new();
35
36 while positions.len() < nnz {
37 let i = rng.gen_range(0..rows);
38 let j = rng.gen_range(0..cols);
39
40 if positions.insert((i, j)) {
41 let val = loop {
42 let v: f64 = rng.gen_range(-10.0..10.0);
43 if v.abs() > 1e-10 { break v;
45 }
46 };
47
48 coo.push(i, j, val);
49 }
50 }
51
52 let actual_density = coo.nnz() as f64 / (rows as f64 * cols as f64);
54 println!("Created sparse matrix: {} x {}", rows, cols);
55 println!(" - Requested density: {:.6}", density);
56 println!(" - Actual density: {:.6}", actual_density);
57 println!(" - Sparsity: {:.4}%", (1.0 - actual_density) * 100.0);
58 println!(" - Non-zeros: {}", coo.nnz());
59
60 coo
61 }
62 fn simple_matrix_comparison() {
64 let mut test_matrix = CooMatrix::<f64>::new(3, 3);
66 test_matrix.push(0, 0, 1.0);
67 test_matrix.push(0, 1, 16.0);
68 test_matrix.push(0, 2, 49.0);
69 test_matrix.push(1, 0, 4.0);
70 test_matrix.push(1, 1, 25.0);
71 test_matrix.push(1, 2, 64.0);
72 test_matrix.push(2, 0, 9.0);
73 test_matrix.push(2, 1, 36.0);
74 test_matrix.push(2, 2, 81.0);
75
76 let seed = 42;
78 let current_result = lanczos::svd_dim_seed(&test_matrix, 0, seed).unwrap();
79 let legacy_result = legacy::svd_dim_seed(&test_matrix, 0, seed).unwrap();
80
81 assert_eq!(current_result.d, legacy_result.d);
83
84 let epsilon = 1.0e-12;
86 for i in 0..current_result.d {
87 let diff = (current_result.s[i] - legacy_result.s[i]).abs();
88 assert!(
89 diff < epsilon,
90 "Singular value {} differs by {}: current = {}, legacy = {}",
91 i, diff, current_result.s[i], legacy_result.s[i]
92 );
93 }
94
95 let current_reconstructed = current_result.recompose();
97 let legacy_reconstructed = legacy_result.recompose();
98
99 for i in 0..3 {
100 for j in 0..3 {
101 let diff = (current_reconstructed[[i, j]] - legacy_reconstructed[[i, j]]).abs();
102 assert!(
103 diff < epsilon,
104 "Reconstructed matrix element [{},{}] differs by {}: current = {}, legacy = {}",
105 i, j, diff, current_reconstructed[[i, j]], legacy_reconstructed[[i, j]]
106 );
107 }
108 }
109 }
110
111 #[test]
112 fn random_matrix_comparison() {
113 let seed = 12345;
114 let (nrows, ncols) = (50, 30);
115 let mut rng = StdRng::seed_from_u64(seed);
116
117 let mut coo = CooMatrix::<f64>::new(nrows, ncols);
119 for _ in 0..(nrows * ncols / 5) { let i = rng.gen_range(0..nrows);
122 let j = rng.gen_range(0..ncols);
123 let value = rng.gen_range(-10.0..10.0);
124 coo.push(i, j, value);
125 }
126
127 let csr = CsrMatrix::from(&coo);
128
129 let legacy_svd = lanczos::svd_dim_seed(&csr, 0, seed as u32).unwrap();
131
132 let mask = vec![true; ncols];
134 let masked_matrix = lanczos::masked::MaskedCSRMatrix::new(&csr, mask);
135 let current_svd = lanczos::svd_dim_seed(&masked_matrix, 0, seed as u32).unwrap();
136
137 let rel_tol = 1e-3; assert_eq!(legacy_svd.d, current_svd.d, "Ranks differ");
141
142 for i in 0..legacy_svd.d {
143 let legacy_val = legacy_svd.s[i];
144 let current_val = current_svd.s[i];
145 let abs_diff = (legacy_val - current_val).abs();
146 let rel_diff = abs_diff / legacy_val.max(current_val);
147
148 assert!(
149 rel_diff <= rel_tol,
150 "Singular value {} differs too much: relative diff = {}, current = {}, legacy = {}",
151 i, rel_diff, current_val, legacy_val
152 );
153 }
154 }
155
156 #[test]
157 fn test_real_sparse_matrix() {
158 let test_matrix = create_sparse_matrix(100, 100, 0.0098); let result = lanczos::svd_dim_seed(&test_matrix, 50, 42);
163 assert!(result.is_ok(), "{}", format!("SVD failed on 99.02% sparse matrix, {:?}", result.err().unwrap()));
164 }
165
166 #[test]
167 fn test_random_svd_computation() {
168
169 let test_matrix = create_sparse_matrix(1000, 250, 0.01); let csr = CsrMatrix::from(&test_matrix);
172
173 let result = randomized::randomized_svd(
174 &csr,
175 50,
176 10,
177 3,
178 randomized::PowerIterationNormalizer::QR,
179 false,
180 Some(42),
181 false
182 );
183
184 assert!(
186 result.is_ok(),
187 "Randomized SVD failed on 99% sparse matrix: {:?}",
188 result.err().unwrap()
189 );
190
191 if let Ok(svd_result) = result {
193 assert_eq!(svd_result.d, 50, "Expected rank of 50");
195
196 for i in 0..svd_result.s.len() {
198 assert!(svd_result.s[i] > 0.0, "Singular values should be positive");
199 if i > 0 {
200 assert!(
201 svd_result.s[i-1] >= svd_result.s[i],
202 "Singular values should be in descending order"
203 );
204 }
205 }
206
207 assert_eq!(svd_result.u.nrows(), 50, "U transpose should have 50 rows");
209 assert_eq!(svd_result.u.ncols(), 1000, "U transpose should have 1000 columns");
210 assert_eq!(svd_result.vt.nrows(), 50, "V transpose should have 50 rows");
211 assert_eq!(svd_result.vt.ncols(), 250, "V transpose should have 250 columns");
212
213 }
214 }
215
216 #[test]
217 fn test_randomized_svd_very_large_sparse_matrix() {
218
219 let test_matrix = create_sparse_matrix(100000, 2500, 0.01); let csr = CsrMatrix::from(&test_matrix);
224
225 let threadpool = ThreadPoolBuilder::new().num_threads(10).build().unwrap();
227 let result = threadpool.install(|| {
228 randomized::randomized_svd(
229 &csr,
230 50, 10, 7, randomized::PowerIterationNormalizer::QR, false,
235 Some(42),
236 false)
238 });
239
240
241 assert!(
243 result.is_ok(),
244 "Randomized SVD failed on 99% sparse matrix: {:?}",
245 result.err().unwrap()
246 );
247 }
248
249 #[test]
250 fn test_randomized_svd_small_sparse_matrix() {
251
252 let test_matrix = create_sparse_matrix(1000, 250, 0.01); let csr = CsrMatrix::from(&test_matrix);
257
258 let threadpool = ThreadPoolBuilder::new().num_threads(10).build().unwrap();
260 let result = threadpool.install(|| {
261 randomized::randomized_svd(
262 &csr,
263 50, 10, 2, randomized::PowerIterationNormalizer::QR, false,
268 Some(42), false
270 )
271 });
272
273
274 assert!(
276 result.is_ok(),
277 "Randomized SVD failed on 99% sparse matrix: {:?}",
278 result.err().unwrap()
279 );
280 }
281}