a/math/matrix/svd.rs
1//! The singular value decomposition: a reduction to a real bidiagonal matrix by Householder
2//! reflections in panels (after one to triangular by QR for a tall matrix), then divide and
3//! conquer on the bidiagonal matrix, whose singular vectors the reflections turn into those of the
4//! matrix; the singular values alone by the dqds algorithm. Small matrices are reduced without
5//! panels, their vectors by the implicit QR algorithm.
6
7mod bidiagonal;
8mod dc;
9mod dqds;
10mod qr;
11mod secular;
12mod small;
13
14use super::properties::range_exponent;
15use super::{Matrix, Scalar};
16use crate::math::complex::Float;
17use bidiagonal::{apply_adjoint_right, reduce, triangularize};
18
19/// The singular value decomposition `A = U·Σ·V*` of an `m × n` matrix, `k = min(m, n)`: `U` of
20/// `m × k` and `V` of `n × k` with orthonormal columns, `Σ` the diagonal of the singular values,
21/// real, not negative, in descending order. From [`Matrix::svd`].
22///
23/// # Examples
24///
25/// ```
26/// use a::math::matrix::Matrix;
27///
28/// let a: Matrix<f64> = Matrix::from_rows(&[[3.0, 0.0], [4.0, 5.0]]);
29/// let svd = a.svd();
30/// let s = svd.singular_values();
31/// assert!((s[0] - 45f64.sqrt()).abs() < 1e-14 && (s[1] - 5f64.sqrt()).abs() < 1e-14);
32/// let sigma = Matrix::from_diagonal(s);
33/// let back = &(&svd.u() * &sigma) * &svd.v().transpose();
34/// assert!((&back - &a).as_slice().iter().all(|x| x.abs() < 1e-14));
35/// ```
36#[derive(Clone, Debug)]
37pub struct Svd<T: Scalar> {
38 /// `U`, `m × k`.
39 u: Matrix<T>,
40 /// The singular values, descending.
41 s: Vec<T::Real>,
42 /// `V`, `n × k`.
43 v: Matrix<T>,
44}
45
46impl<T: Scalar> Svd<T> {
47 /// `U`, `m × min(m, n)`, the left singular vectors as orthonormal columns.
48 pub fn u(&self) -> Matrix<T> {
49 self.u.clone()
50 }
51
52 /// The singular values, real, not negative, in descending order.
53 pub fn singular_values(&self) -> &[T::Real] {
54 &self.s
55 }
56
57 /// `V`, `n × min(m, n)`, the right singular vectors as orthonormal columns.
58 pub fn v(&self) -> Matrix<T> {
59 self.v.clone()
60 }
61}
62
63impl<T: Scalar> Matrix<T> {
64 /// The singular value decomposition `A = U·Σ·V*`: see [`Svd`]. A reduction to a real
65 /// bidiagonal matrix by Householder reflections from both sides, in panels whose updates of
66 /// the rest of the matrix are products of blocks, then divide and conquer on the bidiagonal
67 /// matrix, Gu and Eisenstat's, and the reflections applied to its singular vectors in blocks:
68 /// about `9n³` operations for a square matrix, most of them in products of blocks. Up to
69 /// about a hundred columns (88 to 112 by the type), where that takes longer, the reduction
70 /// is made one column and row at a time, `U` and `V` are multiplied out from the reflections,
71 /// and the implicit QR algorithm's rotations are applied to them. A matrix with at least
72 /// `2.5` times as many rows as columns is first reduced to triangular by QR. A matrix with a
73 /// NaN or an infinity gives NaNs.
74 pub fn svd(&self) -> Svd<T> {
75 let (s, uv) = svd(self, true);
76 let (u, v) = uv.unwrap_or_default();
77 Svd { u, s, v }
78 }
79
80 /// The singular values, real, not negative, in descending order, without `U` and `V`:
81 /// much less work than [`svd`](Matrix::svd). Those of the bidiagonal matrix come from the
82 /// dqds algorithm, to high relative accuracy, so that a bidiagonal matrix has even its
83 /// smallest singular values to nearly full precision; QR goes first from `1.75` times as
84 /// many rows as columns.
85 pub fn singular_values(&self) -> Vec<T::Real> {
86 svd(self, false).0
87 }
88}
89
90/// The singular values of `a`, and `U` and `V` when `want_uv` is set.
91#[allow(clippy::type_complexity)]
92pub(crate) fn svd<T: Scalar>(
93 a: &Matrix<T>,
94 want_uv: bool,
95) -> (Vec<T::Real>, Option<(Matrix<T>, Matrix<T>)>) {
96 let (mut s, uv, k) = svd_scaled(a, want_uv);
97 if k != 0 {
98 let down = T::Real::TWO.powi(-k);
99 for x in &mut s {
100 *x *= down;
101 }
102 }
103 (s, uv)
104}
105
106/// [`svd`] with the singular values of `2^k·a` and `k`, a power of two that brings the largest
107/// element into the range where products of elements neither overflow nor underflow: for what
108/// depends only on the singular values' ratios (ranks, null spaces, condition numbers), which
109/// stay finite where `a`'s own singular values would overflow or underflow.
110#[allow(clippy::type_complexity)]
111pub(crate) fn svd_scaled<T: Scalar>(
112 a: &Matrix<T>,
113 want_uv: bool,
114) -> (Vec<T::Real>, Option<(Matrix<T>, Matrix<T>)>, i32) {
115 let (m, n) = a.shape();
116 // A wide matrix by its conjugate transpose, `A* = V·Σ·U*`.
117 let wide = m < n;
118 let (rows, cols) = if wide { (n, m) } else { (m, n) };
119 let swap = |uv: Option<(Matrix<T>, Matrix<T>)>| {
120 if wide { uv.map(|(u, v)| (v, u)) } else { uv }
121 };
122 if a.data.iter().any(|x| !x.is_finite()) {
123 let nan = T::from_parts(T::Real::NAN, T::Real::NAN);
124 let uv = want_uv.then(|| {
125 (
126 Matrix::from_fn(rows, cols, |_, _| nan),
127 Matrix::from_fn(cols, cols, |_, _| nan),
128 )
129 });
130 return (vec![T::Real::NAN; cols], swap(uv), 0);
131 }
132 // Scaled by a power of two when the largest element is so small or so large that products
133 // of elements could underflow or overflow, as LAPACK's drivers do.
134 let k = range_exponent(&a.data);
135 let mut x = if wide {
136 adjoint(&a.data, m, n).data
137 } else {
138 a.data.clone()
139 };
140 if k != 0 {
141 let up = T::Real::TWO.powi(k);
142 for x in &mut x {
143 *x = x.scale(up);
144 }
145 }
146 let (s, uv) = decompose(x, rows, cols, want_uv);
147 (s, swap(uv), k)
148}
149
150/// [`svd`] of the `m × n` column-major `a`, `m ≥ n`, its elements finite and of a moderate size.
151#[allow(clippy::type_complexity)]
152fn decompose<T: Scalar>(
153 mut a: Vec<T>,
154 m: usize,
155 n: usize,
156 want_uv: bool,
157) -> (Vec<T::Real>, Option<(Matrix<T>, Matrix<T>)>) {
158 // QR first from `m ≥ 2.5n` with the vectors, from `m ≥ 1.75n` without, where it starts to
159 // pay.
160 if n > 0
161 && if want_uv {
162 2 * m >= 5 * n
163 } else {
164 4 * m >= 7 * n
165 }
166 {
167 // `A = Q·R`, and `R = U·Σ·V*`: fewer operations than the reduction of `A` itself.
168 let taus = triangularize(&mut a, m, n);
169 let mut r = vec![T::zero(); n * n];
170 for j in 0..n {
171 r[j * n..j * n + j + 1].copy_from_slice(&a[j * m..j * m + j + 1]);
172 }
173 let (s, uv) = decompose(r, n, n, want_uv);
174 let uv = uv.map(|(ur, v)| {
175 // `U* = [U_R*, 0]·Q*`.
176 let mut x = vec![T::zero(); n * m];
177 for j in 0..n {
178 for (i, x) in x[j * n..(j + 1) * n].iter_mut().enumerate() {
179 *x = ur.data[j + i * n].conj();
180 }
181 }
182 apply_adjoint_right(&a, m, m, &taus, &mut x, n);
183 (adjoint(&x, n, m), v)
184 });
185 return (s, uv);
186 }
187 if n <= small::largest::<T>(want_uv) {
188 return small::decompose(a, m, n, want_uv);
189 }
190 let (mut d, mut e, reduced) = reduce(a, m, n);
191 if !want_uv {
192 if !dqds::singular_values(&mut d, &e) {
193 e.push(T::Real::ZERO);
194 qr::bidiagonal_qr::<_, qr::Columns<_>, qr::Columns<_>>(&mut d, &mut e, None, None);
195 }
196 return (d, None);
197 }
198 let mut ub = vec![T::Real::ZERO; n * n];
199 let mut vb = vec![T::Real::ZERO; n * n];
200 dc::bidiagonal_svd(&mut d, &mut e, &mut ub, &mut vb, T::gemm_real);
201 // `U* = [U_B*, 0]·Q*` and `V* = V_B*·P*`, the reflections applied from the right.
202 let mut x = vec![T::zero(); n * m];
203 let mut y = vec![T::zero(); n * n];
204 for j in 0..n {
205 for i in 0..n {
206 x[i + j * n] = T::from_real(ub[j + i * n]);
207 y[i + j * n] = T::from_real(vb[j + i * n]);
208 }
209 }
210 reduced.apply_q_right(&mut x, n);
211 reduced.apply_p_right(&mut y, n);
212 (d, Some((adjoint(&x, n, m), adjoint(&y, n, n))))
213}
214
215/// The conjugate transpose of the `rows × cols` column-major `x`, in tiles.
216fn adjoint<T: Scalar>(x: &[T], rows: usize, cols: usize) -> Matrix<T> {
217 const TILE: usize = 16;
218 let mut a = Matrix::zeros(cols, rows);
219 for j0 in (0..rows).step_by(TILE) {
220 for i0 in (0..cols).step_by(TILE) {
221 for j in j0..(j0 + TILE).min(rows) {
222 for i in i0..(i0 + TILE).min(cols) {
223 a.data[i + j * cols] = x[j + i * rows].conj();
224 }
225 }
226 }
227 }
228 a
229}