1pub mod legacy;
2pub mod error;
3mod new;
4mod masked;
5
6pub use new::*;
7pub use masked::*;
8
9#[cfg(test)]
10mod simple_comparison_tests {
11 use super::*;
12 use legacy;
13 use nalgebra_sparse::coo::CooMatrix;
14 use nalgebra_sparse::CsrMatrix;
15 use rand::{Rng, SeedableRng};
16 use rand::rngs::StdRng;
17
18 fn create_sparse_matrix(rows: usize, cols: usize, density: f64) -> nalgebra_sparse::coo::CooMatrix<f64> {
19 use rand::{rngs::StdRng, Rng, SeedableRng};
20 use std::collections::HashSet;
21
22 let mut coo = nalgebra_sparse::coo::CooMatrix::new(rows, cols);
23
24 let mut rng = StdRng::seed_from_u64(42);
25
26 let nnz = (rows as f64 * cols as f64 * density).round() as usize;
27
28 let nnz = nnz.max(1);
29
30 let mut positions = HashSet::new();
31
32 while positions.len() < nnz {
33 let i = rng.gen_range(0..rows);
34 let j = rng.gen_range(0..cols);
35
36 if positions.insert((i, j)) {
37 let val = loop {
38 let v: f64 = rng.gen_range(-10.0..10.0);
39 if v.abs() > 1e-10 { break v;
41 }
42 };
43
44 coo.push(i, j, val);
45 }
46 }
47
48 let actual_density = coo.nnz() as f64 / (rows as f64 * cols as f64);
50 println!("Created sparse matrix: {} x {}", rows, cols);
51 println!(" - Requested density: {:.6}", density);
52 println!(" - Actual density: {:.6}", actual_density);
53 println!(" - Sparsity: {:.4}%", (1.0 - actual_density) * 100.0);
54 println!(" - Non-zeros: {}", coo.nnz());
55
56 coo
57 }
58 #[test]
59 fn simple_matrix_comparison() {
60 let mut test_matrix = CooMatrix::<f64>::new(3, 3);
62 test_matrix.push(0, 0, 1.0);
63 test_matrix.push(0, 1, 16.0);
64 test_matrix.push(0, 2, 49.0);
65 test_matrix.push(1, 0, 4.0);
66 test_matrix.push(1, 1, 25.0);
67 test_matrix.push(1, 2, 64.0);
68 test_matrix.push(2, 0, 9.0);
69 test_matrix.push(2, 1, 36.0);
70 test_matrix.push(2, 2, 81.0);
71
72 let seed = 42;
74 let current_result = svd_dim_seed(&test_matrix, 0, seed).unwrap();
75 let legacy_result = legacy::svd_dim_seed(&test_matrix, 0, seed).unwrap();
76
77 assert_eq!(current_result.d, legacy_result.d);
79
80 let epsilon = 1.0e-12;
82 for i in 0..current_result.d {
83 let diff = (current_result.s[i] - legacy_result.s[i]).abs();
84 assert!(
85 diff < epsilon,
86 "Singular value {} differs by {}: current = {}, legacy = {}",
87 i, diff, current_result.s[i], legacy_result.s[i]
88 );
89 }
90
91 let current_reconstructed = current_result.recompose();
93 let legacy_reconstructed = legacy_result.recompose();
94
95 for i in 0..3 {
96 for j in 0..3 {
97 let diff = (current_reconstructed[[i, j]] - legacy_reconstructed[[i, j]]).abs();
98 assert!(
99 diff < epsilon,
100 "Reconstructed matrix element [{},{}] differs by {}: current = {}, legacy = {}",
101 i, j, diff, current_reconstructed[[i, j]], legacy_reconstructed[[i, j]]
102 );
103 }
104 }
105 }
106
107 #[test]
108 fn random_matrix_comparison() {
109 let seed = 12345;
110 let (nrows, ncols) = (50, 30);
111 let mut rng = StdRng::seed_from_u64(seed);
112
113 let mut coo = CooMatrix::<f64>::new(nrows, ncols);
115 for _ in 0..(nrows * ncols / 5) { let i = rng.gen_range(0..nrows);
118 let j = rng.gen_range(0..ncols);
119 let value = rng.gen_range(-10.0..10.0);
120 coo.push(i, j, value);
121 }
122
123 let csr = CsrMatrix::from(&coo);
124
125 let legacy_svd = svd_dim_seed(&csr, 0, seed as u32).unwrap();
127
128 let mask = vec![true; ncols];
130 let masked_matrix = MaskedCSRMatrix::new(&csr, mask);
131 let current_svd = svd_dim_seed(&masked_matrix, 0, seed as u32).unwrap();
132
133 let rel_tol = 1e-3; assert_eq!(legacy_svd.d, current_svd.d, "Ranks differ");
137
138 for i in 0..legacy_svd.d {
139 let legacy_val = legacy_svd.s[i];
140 let current_val = current_svd.s[i];
141 let abs_diff = (legacy_val - current_val).abs();
142 let rel_diff = abs_diff / legacy_val.max(current_val);
143
144 assert!(
145 rel_diff <= rel_tol,
146 "Singular value {} differs too much: relative diff = {}, current = {}, legacy = {}",
147 i, rel_diff, current_val, legacy_val
148 );
149 }
150 }
151
152
153
154 #[test]
155 fn test_real_sparse_matrix() {
156 let test_matrix = create_sparse_matrix(100, 100, 0.0098); let result = svd(&test_matrix); assert!(result.is_ok(), "{}", format!("SVD failed on 99.02% sparse matrix, {:?}", result.err().unwrap()));
162 }
163
164 #[test]
165 fn test_real_sparse_matrix_very_big() {
166 let test_matrix = create_sparse_matrix(10000, 1000, 0.0098); let result = svd(&test_matrix); assert!(result.is_ok(), "{}", format!("SVD failed on 99.02% sparse matrix, {:?}", result.err().unwrap()));
172 }
173
174 #[test]
175 fn f32_precision_test() {
176 let seed = 12345;
177 let (nrows, ncols) = (40, 20);
178 let mut rng = StdRng::seed_from_u64(seed);
179
180 let mut coo_f32 = CooMatrix::<f32>::new(nrows, ncols);
182 let mut coo_f64 = CooMatrix::<f64>::new(nrows, ncols);
184
185 for _ in 0..(nrows * ncols / 4) { let i = rng.gen_range(0..nrows);
188 let j = rng.gen_range(0..ncols);
189 let value = rng.gen_range(-10.0..10.0);
190 coo_f32.push(i, j, value as f32);
191 coo_f64.push(i, j, value);
192 }
193
194 let csr_f32 = CsrMatrix::from(&coo_f32);
195 let csr_f64 = CsrMatrix::from(&coo_f64);
196
197 let svd_f32 = svd_dim_seed(&csr_f32, 0, seed as u32).unwrap();
199 let svd_f64 = svd_dim_seed(&csr_f64, 0, seed as u32).unwrap();
200
201 fn calculate_tolerance(index: usize, magnitude: f64) -> f64 {
204 let base_tol = 0.002;
206
207 let magnitude_factor = if magnitude < 1.0 {
209 2.0 } else if magnitude < 10.0 {
211 1.5 } else {
213 1.0 };
215
216 let index_factor = 1.0 + (index as f64 * 0.001); base_tol * magnitude_factor * index_factor
220 }
221
222 println!("f32 rank: {}, f64 rank: {}", svd_f32.d, svd_f64.d);
223
224 let min_rank = svd_f32.d.min(svd_f64.d);
226 for i in 0..min_rank {
227 let f32_val = svd_f32.s[i];
228 let f64_val = svd_f64.s[i];
229 let abs_diff = (f64_val - f32_val as f64).abs();
230 let rel_diff = abs_diff / f64_val.max(f32_val as f64);
231
232 let tol = calculate_tolerance(i, f64_val);
234
235 println!("Singular value {}: f32 = {}, f64 = {}, rel_diff = {:.6}%, tol = {:.6}%",
236 i, f32_val, f64_val, rel_diff * 100.0, tol * 100.0);
237
238 assert!(
239 rel_diff <= tol,
240 "Singular value {} differs too much: f64 = {}, f32 = {}. Relative diff: {} > tolerance: {}",
241 i, f64_val, f32_val, rel_diff, tol
242 );
243 }
244
245 if min_rank >= 2 {
248 let condition_f32 = svd_f32.s[0] / svd_f32.s[min_rank - 1];
249 let condition_f64 = svd_f64.s[0] / svd_f64.s[min_rank - 1];
250 let condition_rel_diff = ((condition_f64 - condition_f32 as f64) / condition_f64).abs();
251
252 println!("Condition number: f32 = {}, f64 = {}, rel_diff = {:.6}%",
253 condition_f32, condition_f64, condition_rel_diff * 100.0);
254
255 assert!(condition_rel_diff <= 0.05,
257 "Condition numbers differ too much: f32 = {}, f64 = {}",
258 condition_f32, condition_f64);
259 }
260 }
261}