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 #[test]
19 fn simple_matrix_comparison() {
20 let mut test_matrix = CooMatrix::<f64>::new(3, 3);
22 test_matrix.push(0, 0, 1.0);
23 test_matrix.push(0, 1, 16.0);
24 test_matrix.push(0, 2, 49.0);
25 test_matrix.push(1, 0, 4.0);
26 test_matrix.push(1, 1, 25.0);
27 test_matrix.push(1, 2, 64.0);
28 test_matrix.push(2, 0, 9.0);
29 test_matrix.push(2, 1, 36.0);
30 test_matrix.push(2, 2, 81.0);
31
32 let seed = 42;
34 let current_result = svd_dim_seed(&test_matrix, 0, seed).unwrap();
35 let legacy_result = legacy::svd_dim_seed(&test_matrix, 0, seed).unwrap();
36
37 assert_eq!(current_result.d, legacy_result.d);
39
40 let epsilon = 1.0e-12;
42 for i in 0..current_result.d {
43 let diff = (current_result.s[i] - legacy_result.s[i]).abs();
44 assert!(
45 diff < epsilon,
46 "Singular value {} differs by {}: current = {}, legacy = {}",
47 i, diff, current_result.s[i], legacy_result.s[i]
48 );
49 }
50
51 let current_reconstructed = current_result.recompose();
53 let legacy_reconstructed = legacy_result.recompose();
54
55 for i in 0..3 {
56 for j in 0..3 {
57 let diff = (current_reconstructed[[i, j]] - legacy_reconstructed[[i, j]]).abs();
58 assert!(
59 diff < epsilon,
60 "Reconstructed matrix element [{},{}] differs by {}: current = {}, legacy = {}",
61 i, j, diff, current_reconstructed[[i, j]], legacy_reconstructed[[i, j]]
62 );
63 }
64 }
65 }
66
67 #[test]
68 fn random_matrix_comparison() {
69 let seed = 12345;
70 let (nrows, ncols) = (50, 30);
71 let mut rng = StdRng::seed_from_u64(seed);
72
73 let mut coo = CooMatrix::<f64>::new(nrows, ncols);
75 for _ in 0..(nrows * ncols / 5) { let i = rng.gen_range(0..nrows);
78 let j = rng.gen_range(0..ncols);
79 let value = rng.gen_range(-10.0..10.0);
80 coo.push(i, j, value);
81 }
82
83 let csr = CsrMatrix::from(&coo);
84
85 let legacy_svd = svd_dim_seed(&csr, 0, seed as u32).unwrap();
87
88 let mask = vec![true; ncols];
90 let masked_matrix = MaskedCSRMatrix::new(&csr, mask);
91 let current_svd = svd_dim_seed(&masked_matrix, 0, seed as u32).unwrap();
92
93 let rel_tol = 1e-3; assert_eq!(legacy_svd.d, current_svd.d, "Ranks differ");
97
98 for i in 0..legacy_svd.d {
99 let legacy_val = legacy_svd.s[i];
100 let current_val = current_svd.s[i];
101 let abs_diff = (legacy_val - current_val).abs();
102 let rel_diff = abs_diff / legacy_val.max(current_val);
103
104 assert!(
105 rel_diff <= rel_tol,
106 "Singular value {} differs too much: relative diff = {}, current = {}, legacy = {}",
107 i, rel_diff, current_val, legacy_val
108 );
109 }
110 }
111
112 #[test]
113 fn f32_precision_test() {
114 let seed = 12345;
115 let (nrows, ncols) = (40, 20);
116 let mut rng = StdRng::seed_from_u64(seed);
117
118 let mut coo_f32 = CooMatrix::<f32>::new(nrows, ncols);
120 let mut coo_f64 = CooMatrix::<f64>::new(nrows, ncols);
122
123 for _ in 0..(nrows * ncols / 4) { let i = rng.gen_range(0..nrows);
126 let j = rng.gen_range(0..ncols);
127 let value = rng.gen_range(-10.0..10.0);
128 coo_f32.push(i, j, value as f32);
129 coo_f64.push(i, j, value);
130 }
131
132 let csr_f32 = CsrMatrix::from(&coo_f32);
133 let csr_f64 = CsrMatrix::from(&coo_f64);
134
135 let svd_f32 = svd_dim_seed(&csr_f32, 0, seed as u32).unwrap();
137 let svd_f64 = svd_dim_seed(&csr_f64, 0, seed as u32).unwrap();
138
139 fn calculate_tolerance(index: usize, magnitude: f64) -> f64 {
142 let base_tol = 0.002;
144
145 let magnitude_factor = if magnitude < 1.0 {
147 2.0 } else if magnitude < 10.0 {
149 1.5 } else {
151 1.0 };
153
154 let index_factor = 1.0 + (index as f64 * 0.001); base_tol * magnitude_factor * index_factor
158 }
159
160 println!("f32 rank: {}, f64 rank: {}", svd_f32.d, svd_f64.d);
161
162 let min_rank = svd_f32.d.min(svd_f64.d);
164 for i in 0..min_rank {
165 let f32_val = svd_f32.s[i];
166 let f64_val = svd_f64.s[i];
167 let abs_diff = (f64_val - f32_val as f64).abs();
168 let rel_diff = abs_diff / f64_val.max(f32_val as f64);
169
170 let tol = calculate_tolerance(i, f64_val);
172
173 println!("Singular value {}: f32 = {}, f64 = {}, rel_diff = {:.6}%, tol = {:.6}%",
174 i, f32_val, f64_val, rel_diff * 100.0, tol * 100.0);
175
176 assert!(
177 rel_diff <= tol,
178 "Singular value {} differs too much: f64 = {}, f32 = {}. Relative diff: {} > tolerance: {}",
179 i, f64_val, f32_val, rel_diff, tol
180 );
181 }
182
183 if min_rank >= 2 {
186 let condition_f32 = svd_f32.s[0] / svd_f32.s[min_rank - 1];
187 let condition_f64 = svd_f64.s[0] / svd_f64.s[min_rank - 1];
188 let condition_rel_diff = ((condition_f64 - condition_f32 as f64) / condition_f64).abs();
189
190 println!("Condition number: f32 = {}, f64 = {}, rel_diff = {:.6}%",
191 condition_f32, condition_f64, condition_rel_diff * 100.0);
192
193 assert!(condition_rel_diff <= 0.05,
195 "Condition numbers differ too much: f32 = {}, f64 = {}",
196 condition_f32, condition_f64);
197 }
198 }
199}