Skip to main content

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}