single_svdlib/dense/
mod.rs1pub mod jacobi;
5pub mod tsqr;
6
7pub use tsqr::{orthogonality_error, orthonormalize, tsqr};
8
9use crate::error::{Result, SvdLibError};
10use crate::types::SvdFloat;
11use ndarray::{Array1, Array2, ArrayView2};
12
13pub struct SmallSvd<T> {
15 pub u: Array2<T>,
17 pub s: Array1<T>,
19 pub vt: Array2<T>,
21}
22
23pub fn small_svd<T: SvdFloat>(a: ArrayView2<T>) -> Result<SmallSvd<T>> {
37 let (m, n) = a.dim();
38 if m == 0 || n == 0 {
39 return Err(SvdLibError::shape(format!(
40 "small_svd needs a non-empty matrix, got {m}x{n}"
41 )));
42 }
43 let k = m.min(n);
44
45 if let Some((i, j)) = a
48 .indexed_iter()
49 .find(|(_, v)| !num_traits::Float::is_finite(**v))
50 .map(|(idx, _)| idx)
51 {
52 return Err(SvdLibError::DenseFactorization {
53 factorization: "SVD",
54 message: format!("operand is not finite at ({i}, {j})"),
55 });
56 }
57
58 let a64 = Array2::<f64>::from_shape_fn((m, n), |(i, j)| a[[i, j]].to_f64());
59 let fail = || SvdLibError::DenseFactorization {
60 factorization: "SVD",
61 message: format!("one-sided Jacobi did not converge on a {m}x{n} operand"),
62 };
63
64 let (u64, s64, vt64) = if m >= n {
67 let j = jacobi::jacobi_svd(&a64).ok_or_else(fail)?;
68 (j.u, j.s, j.v.t().to_owned())
69 } else {
70 let j = jacobi::jacobi_svd(&a64.t().to_owned()).ok_or_else(fail)?;
71 (j.v, j.s, j.u.t().to_owned())
72 };
73 debug_assert_eq!(u64.dim(), (m, k));
74 debug_assert_eq!(vt64.dim(), (k, n));
75
76 let mut out_u = Array2::<T>::zeros((m, k));
78 let mut out_s = Array1::<T>::zeros(k);
79 let mut out_vt = Array2::<T>::zeros((k, n));
80 for idx in 0..k {
81 out_s[idx] = T::from_f64_val(s64[idx]);
82 for i in 0..m {
83 out_u[[i, idx]] = T::from_f64_val(u64[[i, idx]]);
84 }
85 for j in 0..n {
86 out_vt[[idx, j]] = T::from_f64_val(vt64[[idx, j]]);
87 }
88 }
89 Ok(SmallSvd {
90 u: out_u,
91 s: out_s,
92 vt: out_vt,
93 })
94}
95
96pub fn svd_flip<T: SvdFloat>(u: &mut Array2<T>, vt: &mut Array2<T>) {
103 let k = u.ncols().min(vt.nrows());
104 for j in 0..k {
105 let mut best = T::zero();
107 let mut sign = T::one();
108 for i in 0..u.nrows() {
109 let v = u[[i, j]];
110 let mag = num_traits::Float::abs(v);
111 if mag > best {
112 best = mag;
113 sign = if v < T::zero() { -T::one() } else { T::one() };
114 }
115 }
116 if sign < T::zero() {
117 for i in 0..u.nrows() {
118 u[[i, j]] = -u[[i, j]];
119 }
120 for j2 in 0..vt.ncols() {
121 vt[[j, j2]] = -vt[[j, j2]];
122 }
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::testing::Lcg;
131
132 #[test]
133 fn small_svd_reconstructs_and_is_ordered() {
134 let mut rng = Lcg::new(11);
135 let a = Array2::from_shape_fn((9, 6), |_| rng.signed());
136 let svd = small_svd(a.view()).unwrap();
137
138 assert_eq!(svd.u.dim(), (9, 6));
139 assert_eq!(svd.s.len(), 6);
140 assert_eq!(svd.vt.dim(), (6, 6));
141
142 for w in svd.s.to_vec().windows(2) {
143 assert!(w[0] >= w[1], "singular values not descending: {:?}", svd.s);
144 }
145
146 let scaled = &svd.u * &svd.s.view().insert_axis(ndarray::Axis(0));
147 let recon = scaled.dot(&svd.vt);
148 let err: f64 = (&recon - &a).iter().map(|v| v * v).sum::<f64>().sqrt();
149 let scale: f64 = a.iter().map(|v| v * v).sum::<f64>().sqrt();
150 assert!(
151 err / scale < 1e-12,
152 "relative reconstruction {}",
153 err / scale
154 );
155 }
156
157 #[test]
159 fn small_svd_promotes_f32() {
160 let mut rng = Lcg::new(13);
161 let a = Array2::from_shape_fn((7, 5), |_| rng.signed() as f32);
162 let svd = small_svd(a.view()).unwrap();
163 let scaled = &svd.u * &svd.s.view().insert_axis(ndarray::Axis(0));
164 let recon = scaled.dot(&svd.vt);
165 let err: f32 = (&recon - &a).iter().map(|v| v * v).sum::<f32>().sqrt();
166 let scale: f32 = a.iter().map(|v| v * v).sum::<f32>().sqrt();
167 assert!(
168 err / scale < 1e-5,
169 "relative reconstruction {}",
170 err / scale
171 );
172 }
173
174 #[test]
175 fn svd_flip_makes_signs_deterministic() {
176 let mut u = ndarray::arr2(&[[-3.0f64, 1.0], [1.0, -4.0]]);
177 let mut vt = ndarray::arr2(&[[1.0f64, 2.0], [3.0, 4.0]]);
178 let before = u.dot(&vt);
179 svd_flip(&mut u, &mut vt);
180 assert!(u[[0, 0]] > 0.0);
182 assert!(u[[1, 1]] > 0.0);
183 let after = u.dot(&vt);
186 for (a, b) in before.iter().zip(after.iter()) {
187 approx::assert_relative_eq!(a, b, max_relative = 1e-14);
188 }
189 }
190
191 #[test]
192 fn small_svd_rejects_empty() {
193 let a = Array2::<f64>::zeros((0, 3));
194 assert!(small_svd(a.view()).is_err());
195 }
196}