Skip to content

Commit ef2f5b5

Browse files
author
Ian
committed
Reorganized structure into multiple modules
1 parent f834a27 commit ef2f5b5

4 files changed

Lines changed: 134 additions & 132 deletions

File tree

src/masked.rs renamed to src/laczos/masked.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<'a, T: Float + AddAssign + Sync + Send> SMat<T> for MaskedCSRMatrix<'a, T>
7676

7777
for i in 0..self.matrix.nrows() {
7878
for j in major_offsets[i]..major_offsets[i + 1] {
79-
let col = minor_indices[j];
79+
let col = minor_indices[j];
8080
if self.column_mask[col] {
8181
count += 1;
8282
}
@@ -213,7 +213,7 @@ impl<'a, T: Float + AddAssign + Sync + Send> SMat<T> for MaskedCSRMatrix<'a, T>
213213
#[cfg(test)]
214214
mod tests {
215215
use super::*;
216-
use crate::{svd, SMat};
216+
use crate::{SMat};
217217
use nalgebra_sparse::{coo::CooMatrix, csr::CsrMatrix};
218218
use rand::rngs::StdRng;
219219
use rand::{Rng, SeedableRng};
@@ -243,7 +243,7 @@ mod tests {
243243
assert_eq!(masked.nnz(), 6); // Only entries in the selected columns
244244

245245
// Test SVD on the masked matrix
246-
let svd_result = svd(&masked);
246+
let svd_result = crate::laczos::svd(&masked);
247247
assert!(svd_result.is_ok());
248248
}
249249

@@ -304,8 +304,8 @@ mod tests {
304304
assert_eq!(masked_matrix.nnz(), physical_csr.nnz());
305305

306306
// Perform SVD on both
307-
let svd_masked = svd(&masked_matrix).unwrap();
308-
let svd_physical = svd(&physical_csr).unwrap();
307+
let svd_masked = crate::laczos::svd(&masked_matrix).unwrap();
308+
let svd_physical = crate::laczos::svd(&physical_csr).unwrap();
309309

310310
// Compare SVD results - they should be very close but not exactly the same
311311
// due to potential differences in numerical computation

src/new.rs renamed to src/laczos/mod.rs

Lines changed: 6 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
pub mod masked;
2+
13
use crate::error::SvdLibError;
2-
use ndarray::{Array, Array1, Array2};
4+
use crate::{Diagnostics, SMat, SvdFloat, SvdRec};
5+
use ndarray::{Array, Array2};
36
use num_traits::real::Real;
47
use num_traits::{Float, FromPrimitive, One, Zero};
58
use rand::rngs::StdRng;
@@ -12,106 +15,7 @@ use std::iter::Sum;
1215
use std::mem;
1316
use std::ops::{AddAssign, MulAssign, Neg, SubAssign};
1417

15-
pub trait SMat<T: Float>: Sync {
16-
fn nrows(&self) -> usize;
17-
fn ncols(&self) -> usize;
18-
fn nnz(&self) -> usize;
19-
fn svd_opa(&self, x: &[T], y: &mut [T], transposed: bool); // y = A*x
20-
}
21-
22-
/// Singular Value Decomposition Components
23-
///
24-
/// # Fields
25-
/// - d: Dimensionality (rank), the number of rows of both `ut`, `vt` and the length of `s`
26-
/// - ut: Transpose of left singular vectors, the vectors are the rows of `ut`
27-
/// - s: Singular values (length `d`)
28-
/// - vt: Transpose of right singular vectors, the vectors are the rows of `vt`
29-
/// - diagnostics: Computational diagnostics
30-
#[derive(Debug, Clone, PartialEq)]
31-
pub struct SvdRec<T: Float> {
32-
pub d: usize,
33-
pub ut: Array2<T>,
34-
pub s: Array1<T>,
35-
pub vt: Array2<T>,
36-
pub diagnostics: Diagnostics<T>,
37-
}
38-
39-
/// Computational Diagnostics
40-
///
41-
/// # Fields
42-
/// - non_zero: Number of non-zeros in the matrix
43-
/// - dimensions: Number of dimensions attempted (bounded by matrix shape)
44-
/// - iterations: Number of iterations attempted (bounded by dimensions and matrix shape)
45-
/// - transposed: True if the matrix was transposed internally
46-
/// - lanczos_steps: Number of Lanczos steps performed
47-
/// - ritz_values_stabilized: Number of ritz values
48-
/// - significant_values: Number of significant values discovered
49-
/// - singular_values: Number of singular values returned
50-
/// - end_interval: left, right end of interval containing unwanted eigenvalues
51-
/// - kappa: relative accuracy of ritz values acceptable as eigenvalues
52-
/// - random_seed: Random seed provided or the seed generated
53-
#[derive(Debug, Clone, PartialEq)]
54-
pub struct Diagnostics<T: Float> {
55-
pub non_zero: usize,
56-
pub dimensions: usize,
57-
pub iterations: usize,
58-
pub transposed: bool,
59-
pub lanczos_steps: usize,
60-
pub ritz_values_stabilized: usize,
61-
pub significant_values: usize,
62-
pub singular_values: usize,
63-
pub end_interval: [T; 2],
64-
pub kappa: T,
65-
pub random_seed: u32,
66-
}
67-
6818
/// Trait for floating point types that can be used with the SVD algorithm
69-
pub trait SvdFloat:
70-
Float
71-
+ FromPrimitive
72-
+ Debug
73-
+ Send
74-
+ Sync
75-
+ Zero
76-
+ One
77-
+ AddAssign
78-
+ SubAssign
79-
+ MulAssign
80-
+ Neg<Output = Self>
81-
+ Sum
82-
{
83-
fn eps() -> Self;
84-
fn eps34() -> Self;
85-
fn compare(a: Self, b: Self) -> bool;
86-
}
87-
88-
impl SvdFloat for f32 {
89-
fn eps() -> Self {
90-
f32::EPSILON
91-
}
92-
93-
fn eps34() -> Self {
94-
f32::EPSILON.powf(0.75)
95-
}
96-
97-
fn compare(a: Self, b: Self) -> bool {
98-
(b - a).abs() < f32::EPSILON
99-
}
100-
}
101-
102-
impl SvdFloat for f64 {
103-
fn eps() -> Self {
104-
f64::EPSILON
105-
}
106-
107-
fn eps34() -> Self {
108-
f64::EPSILON.powf(0.75)
109-
}
110-
111-
fn compare(a: Self, b: Self) -> bool {
112-
(b - a).abs() < f64::EPSILON
113-
}
114-
}
11519
11620
/// SVD at full dimensionality, calls `svdLAS2` with the highlighted defaults
11721
///
@@ -1229,7 +1133,6 @@ fn ritvec<T: SvdFloat>(
12291133
let significant_indices: Vec<usize> = (0..js)
12301134
.into_par_iter()
12311135
.filter(|&k| {
1232-
// Adaptive error bound check using relative tolerance
12331136
let relative_bound =
12341137
adaptive_kappa * wrk.ritz[k].abs().max(max_eigenvalue * adaptive_eps);
12351138
wrk.bnd[k] <= relative_bound && k + 1 > js - neig
@@ -1242,19 +1145,17 @@ fn ritvec<T: SvdFloat>(
12421145
.into_par_iter()
12431146
.map(|k| {
12441147
let mut vec = vec![T::zero(); wrk.ncols];
1245-
let mut idx = (jsq - js) + k + 1;
12461148

12471149
for i in 0..js {
1248-
idx -= js;
1249-
// Non-zero check with adaptive threshold
1150+
let idx = k * js + i;
1151+
12501152
if s[idx].abs() > adaptive_eps {
12511153
for (j, item) in store_vectors[i].iter().enumerate().take(wrk.ncols) {
12521154
vec[j] += s[idx] * *item;
12531155
}
12541156
}
12551157
}
12561158

1257-
// Return with position index (for proper ordering)
12581159
(k, vec)
12591160
})
12601161
.collect();

src/lib.rs

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
pub mod legacy;
22
pub mod error;
3-
mod new;
4-
mod masked;
53
pub(crate) mod utils;
64

7-
pub(crate) mod randomized;
5+
pub mod randomized;
6+
7+
pub mod laczos;
8+
9+
pub use utils::*;
810

9-
pub use new::*;
10-
pub use masked::*;
11-
pub use randomized::{randomized_svd, PowerIterationNormalizer};
1211

1312
#[cfg(test)]
1413
mod simple_comparison_tests {
@@ -19,7 +18,6 @@ mod simple_comparison_tests {
1918
use rand::{Rng, SeedableRng};
2019
use rand::rngs::StdRng;
2120
use rayon::ThreadPoolBuilder;
22-
use crate::randomized::randomized_svd;
2321

2422
fn create_sparse_matrix(rows: usize, cols: usize, density: f64) -> nalgebra_sparse::coo::CooMatrix<f64> {
2523
use rand::{rngs::StdRng, Rng, SeedableRng};
@@ -77,7 +75,7 @@ mod simple_comparison_tests {
7775

7876
// Run both implementations with the same seed for deterministic behavior
7977
let seed = 42;
80-
let current_result = svd_dim_seed(&test_matrix, 0, seed).unwrap();
78+
let current_result = laczos::svd_dim_seed(&test_matrix, 0, seed).unwrap();
8179
let legacy_result = legacy::svd_dim_seed(&test_matrix, 0, seed).unwrap();
8280

8381
// Compare dimensions
@@ -129,12 +127,12 @@ mod simple_comparison_tests {
129127
let csr = CsrMatrix::from(&coo);
130128

131129
// Calculate SVD using original method
132-
let legacy_svd = svd_dim_seed(&csr, 0, seed as u32).unwrap();
130+
let legacy_svd = laczos::svd_dim_seed(&csr, 0, seed as u32).unwrap();
133131

134132
// Calculate SVD using our masked method (using all columns)
135133
let mask = vec![true; ncols];
136-
let masked_matrix = MaskedCSRMatrix::new(&csr, mask);
137-
let current_svd = svd_dim_seed(&masked_matrix, 0, seed as u32).unwrap();
134+
let masked_matrix = laczos::masked::MaskedCSRMatrix::new(&csr, mask);
135+
let current_svd = laczos::svd_dim_seed(&masked_matrix, 0, seed as u32).unwrap();
138136

139137
// Compare with relative tolerance
140138
let rel_tol = 1e-3; // 0.1% relative tolerance
@@ -161,13 +159,12 @@ mod simple_comparison_tests {
161159
let test_matrix = create_sparse_matrix(100, 100, 0.0098); // 0.98% non-zeros
162160

163161
// Should no longer fail with convergence error
164-
let result = svd_dim_seed(&test_matrix, 50, 42);
162+
let result = laczos::svd_dim_seed(&test_matrix, 50, 42);
165163
assert!(result.is_ok(), "{}", format!("SVD failed on 99.02% sparse matrix, {:?}", result.err().unwrap()));
166164
}
167165

168166
#[test]
169167
fn test_random_svd_computation() {
170-
use crate::{randomized_svd, PowerIterationNormalizer};
171168

172169
// Create a matrix with high sparsity (99%)
173170
let test_matrix = create_sparse_matrix(1000, 250, 0.01); // 1% non-zeros
@@ -176,12 +173,12 @@ mod simple_comparison_tests {
176173
let csr = CsrMatrix::from(&test_matrix);
177174

178175
// Run randomized SVD with reasonable defaults for a sparse matrix
179-
let result = randomized_svd(
176+
let result = randomized::randomized_svd(
180177
&csr,
181178
50, // target rank
182179
10, // oversampling parameter
183180
3, // power iterations
184-
PowerIterationNormalizer::QR, // use QR normalization
181+
randomized::PowerIterationNormalizer::QR, // use QR normalization
185182
Some(42), // random seed
186183
);
187184

@@ -219,7 +216,6 @@ mod simple_comparison_tests {
219216

220217
#[test]
221218
fn test_randomized_svd_very_large_sparse_matrix() {
222-
use crate::{randomized_svd, PowerIterationNormalizer};
223219

224220
// Create a very large matrix with high sparsity (99%)
225221
let test_matrix = create_sparse_matrix(100000, 2500, 0.01); // 1% non-zeros
@@ -230,12 +226,12 @@ mod simple_comparison_tests {
230226
// Run randomized SVD with reasonable defaults for a sparse matrix
231227
let threadpool = ThreadPoolBuilder::new().num_threads(10).build().unwrap();
232228
let result = threadpool.install(|| {
233-
randomized_svd(
229+
randomized::randomized_svd(
234230
&csr,
235231
50, // target rank
236232
10, // oversampling parameter
237-
2, // power iterations
238-
PowerIterationNormalizer::QR, // use QR normalization
233+
7, // power iterations
234+
randomized::PowerIterationNormalizer::QR, // use QR normalization
239235
Some(42), // random seed
240236
)
241237
});
@@ -251,7 +247,6 @@ mod simple_comparison_tests {
251247

252248
#[test]
253249
fn test_randomized_svd_small_sparse_matrix() {
254-
use crate::{randomized_svd, PowerIterationNormalizer};
255250

256251
// Create a very large matrix with high sparsity (99%)
257252
let test_matrix = create_sparse_matrix(1000, 250, 0.01); // 1% non-zeros
@@ -262,12 +257,12 @@ mod simple_comparison_tests {
262257
// Run randomized SVD with reasonable defaults for a sparse matrix
263258
let threadpool = ThreadPoolBuilder::new().num_threads(10).build().unwrap();
264259
let result = threadpool.install(|| {
265-
randomized_svd(
260+
randomized::randomized_svd(
266261
&csr,
267262
50, // target rank
268263
10, // oversampling parameter
269264
2, // power iterations
270-
PowerIterationNormalizer::QR, // use QR normalization
265+
randomized::PowerIterationNormalizer::QR, // use QR normalization
271266
Some(42), // random seed
272267
)
273268
});

0 commit comments

Comments
 (0)