#![allow(clippy::needless_range_loop)]
pub mod dense;
pub mod error;
pub mod irlba;
#[cfg(feature = "las2")]
pub mod lanczos;
pub mod matrix;
pub mod randomized;
pub mod types;
#[cfg(test)]
mod testing;
pub use error::{Result, SvdLibError};
pub use matrix::{
MaskedCsMat, SparseMat, SparseMatDense, SvdMat, SvdMatView, DEFAULT_SCRATCH_BUDGET,
};
pub use types::{Algorithm, Detail, Diagnostics, SvdFloat, SvdRec};
pub use sprs;
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;
pub fn svd<T: SvdFloat, M: SparseMat<T>>(a: &M, rank: usize) -> Result<SvdRec<T>> {
irlba::svd(a, rank)
}
pub fn svd_seed<T: SvdFloat, M: SparseMat<T>>(a: &M, rank: usize, seed: u64) -> Result<SvdRec<T>> {
irlba::svd_seed(a, rank, seed)
}
pub fn svd_centered<T: SvdFloat, M: SparseMatDense<T>>(
a: &M,
rank: usize,
seed: Option<u64>,
) -> Result<SvdRec<T>> {
irlba::svd_centered(a, rank, seed)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{dense_of, gen_lowrank, gen_sparse, reference_singular_values};
#[test]
fn all_solvers_agree_with_lapack() {
let a = gen_lowrank(300, 100, 10, 101);
let want = reference_singular_values(&dense_of(&a));
let rank = 10;
let by_irlba = irlba::svd_seed(&a, rank, 42).unwrap();
let by_random = randomized::svd_with(
&a,
&randomized::RandomizedConfig::new(rank)
.seed(42)
.power_iterations(4),
None,
)
.unwrap();
let by_default = svd_seed(&a, rank, 42).unwrap();
for i in 0..rank {
for (name, got, tol) in [
("irlba", by_irlba.s[i], 1e-9),
("randomized", by_random.s[i], 1e-6),
("top-level default", by_default.s[i], 1e-9),
] {
let rel = (got - want[i]).abs() / want[i];
assert!(
rel < tol,
"{name} triplet {i}: {got:.12e} vs LAPACK {:.12e} (rel {rel:.3e})",
want[i]
);
}
}
}
#[test]
fn top_level_dispatches_to_irlba() {
let a = gen_sparse(120, 60, 0.1, 7);
let got = svd(&a, 5).unwrap();
assert_eq!(got.diagnostics.algorithm, Algorithm::Irlba);
}
#[test]
fn index_width_does_not_change_results() {
use sprs::TriMatI;
let a32 = gen_sparse(200, 80, 0.08, 13);
let mut t = TriMatI::<f64, u64>::new((200, 80));
for (v, (i, j)) in a32.iter() {
t.add_triplet(i as usize, j as usize, *v);
}
let a64: SvdMat<f64, u64, u64> = t.to_csr::<u64>();
let x = svd_seed(&a32, 10, 42).unwrap();
let y = svd_seed(&a64, 10, 42).unwrap();
for (p, q) in x.s.iter().zip(y.s.iter()) {
approx::assert_relative_eq!(p, q, max_relative = 1e-12);
}
}
#[test]
fn u32_indices_are_smaller_than_usize_indices() {
let a = gen_sparse(2000, 500, 0.02, 3);
let nnz = a.nnz();
let rows = a.rows();
assert_eq!(a.indices().len(), nnz);
assert_eq!(a.data().len(), nnz);
let ours = (rows + 1) * std::mem::size_of::<u64>()
+ nnz * std::mem::size_of::<u32>()
+ nnz * std::mem::size_of::<f64>();
let usize_everywhere = (rows + 1) * std::mem::size_of::<usize>()
+ nnz * std::mem::size_of::<usize>()
+ nnz * std::mem::size_of::<f64>();
let saving = 1.0 - (ours as f64 / usize_everywhere as f64);
assert!(
saving > 0.2,
"expected >20% smaller, got {:.1}%",
saving * 100.0
);
}
}