Skip to main content

a/math/matrix/
functions.rs

1//! Functions of matrices: the exponential, the square root and the logarithm.
2
3use super::blas::{Op, Side, Uplo, gemm, trsm};
4use super::eigen::real_to_complex_schur;
5use super::element::sealed::{Element as _, Scalar as _};
6use super::schur::schur;
7use super::{Matrix, Norm, Scalar};
8use crate::math::complex::Float;
9
10/// The coefficients of the Padé approximants of `e^x` of degree 3, 5, 7, 9 and 13, from the
11/// constant term up, and the largest 1-norm each is accurate for in double precision (Higham,
12/// 2005).
13const PADE: [(&[f64], f64); 5] = [
14    (&[120.0, 60.0, 12.0, 1.0], 1.495_585_217_958_292e-2),
15    (
16        &[30240.0, 15120.0, 3360.0, 420.0, 30.0, 1.0],
17        2.539_398_330_063_23e-1,
18    ),
19    (
20        &[
21            17_297_280.0,
22            8_648_640.0,
23            1_995_840.0,
24            277_200.0,
25            25200.0,
26            1512.0,
27            56.0,
28            1.0,
29        ],
30        9.504_178_996_162_932e-1,
31    ),
32    (
33        &[
34            17_643_225_600.0,
35            8_821_612_800.0,
36            2_075_673_600.0,
37            302_702_400.0,
38            30_270_240.0,
39            2_162_160.0,
40            110_880.0,
41            3960.0,
42            90.0,
43            1.0,
44        ],
45        2.097_847_961_257_068,
46    ),
47    (
48        &[
49            64_764_752_532_480_000.0,
50            32_382_376_266_240_000.0,
51            7_771_770_303_897_600.0,
52            1_187_353_796_428_800.0,
53            129_060_195_264_000.0,
54            10_559_470_521_600.0,
55            670_442_572_800.0,
56            33_522_128_640.0,
57            1_323_241_920.0,
58            40_840_800.0,
59            960_960.0,
60            16380.0,
61            182.0,
62            1.0,
63        ],
64        5.371_920_351_148_152,
65    ),
66];
67
68/// In double precision: the degrees 3, 5, 7 and 9, by their places in [`PADE`], and their bounds,
69/// below 13's.
70const PADE_DOUBLE: [(usize, f64); 4] = [
71    (0, PADE[0].1),
72    (1, PADE[1].1),
73    (2, PADE[2].1),
74    (3, PADE[3].1),
75];
76
77/// In single precision: the degrees 3, 5 and 7 and their bounds.
78const PADE_SINGLE: [(usize, f64); 3] = [
79    (0, 4.258_730_016_922_831e-1),
80    (1, 1.880_152_677_804_762),
81    (2, 3.925_724_783_138_66),
82];
83
84/// The nodes and weights of Gauss and Legendre's rule of 7 points on `[0, 1]`.
85const GAUSS: [(f64, f64); 7] = [
86    (0.025_446_043_828_620_74, 0.064_742_483_084_434_85),
87    (0.129_234_407_200_302_78, 0.139_852_695_744_638_33),
88    (0.297_077_424_311_301_4, 0.190_915_025_252_559_47),
89    (0.5, 0.208_979_591_836_734_7),
90    (0.702_922_575_688_698_6, 0.190_915_025_252_559_47),
91    (0.870_765_592_799_697_2, 0.139_852_695_744_638_33),
92    (0.974_553_956_171_379_3, 0.064_742_483_084_434_85),
93];
94
95/// How close to `I` the repeated square roots take `T` before the logarithm's Padé
96/// approximant of degree 7, in the 1-norm (Higham, 2001).
97const LOG_THETA: f64 = 0.264;
98
99impl<T: Scalar> Matrix<T> {
100    /// The exponential `e^A = I + A + A²/2! + …`, by scaling and squaring: `A / 2^s` small
101    /// enough for a Padé approximant of degree 3 to 13, chosen by its 1-norm (7 in single
102    /// precision), then squared `s` times, as Higham's `expm` (2005).
103    ///
104    /// # Panics
105    ///
106    /// Panics if the matrix is not square.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use a::math::matrix::Matrix;
112    ///
113    /// // The generator of rotations: e^A rotates by θ.
114    /// let t = 0.5f64;
115    /// let a = Matrix::from_rows(&[[0.0, -t], [t, 0.0]]);
116    /// let r = Matrix::from_rows(&[[t.cos(), -t.sin()], [t.sin(), t.cos()]]);
117    /// assert!((&a.exp() - &r).as_slice().iter().all(|x| x.abs() < 1e-15));
118    /// ```
119    #[track_caller]
120    pub fn exp(&self) -> Self {
121        assert!(self.is_square(), "the matrix is not square");
122        let n = self.rows();
123        if self.data.iter().any(|x| !x.is_finite()) {
124            let nan = T::from_parts(T::Real::NAN, T::Real::NAN);
125            return Matrix::from_fn(n, n, |_, _| nan);
126        }
127        let norm = self.norm(Norm::One).to_f64();
128        let single = T::Real::MANTISSA_DIGITS <= 24;
129        let (table, top): (&[(usize, f64)], usize) = if single {
130            (&PADE_SINGLE, 2)
131        } else {
132            (&PADE_DOUBLE, 4)
133        };
134        for &(d, theta) in table {
135            if norm <= theta {
136                return pade(self, PADE[d].0);
137            }
138        }
139        let theta = if single { PADE_SINGLE[2].1 } else { PADE[4].1 };
140        // A norm that overflows the elements' type, of finite elements, is at most `n` times the
141        // largest part times √2: its logarithm from that keeps the squarings few, where `∞` would
142        // make them `i32::MAX`.
143        let log2 = if norm.is_finite() {
144            (norm / theta).log2()
145        } else {
146            let big = self.data.iter().fold(0.0f64, |s, x| {
147                s.max(x.re().abs().to_f64().max(x.im().abs().to_f64()))
148            });
149            (n as f64).log2() + big.log2() + 0.5 - theta.log2()
150        };
151        let s = log2.ceil().max(0.0) as i32;
152        let scaled = self.map(|&x| x.scale(T::Real::from_f64(0.5f64.powi(s))));
153        let mut x = pade(&scaled, PADE[top].0);
154        for _ in 0..s {
155            x = &x * &x;
156        }
157        x
158    }
159
160    /// The principal square root `√A`, whose eigenvalues have positive real parts, by the
161    /// Schur method: `R² = T` solved for an upper triangular `R` element by element, then
162    /// `Q·R·Q*`. `None` for a real matrix with a negative real eigenvalue, whose square roots
163    /// are not real, or for a singular matrix that has none.
164    ///
165    /// # Panics
166    ///
167    /// Panics if the matrix is not square.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// use a::math::matrix::Matrix;
173    ///
174    /// let a: Matrix<f64> = Matrix::from_rows(&[[33.0, 24.0], [48.0, 57.0]]);
175    /// let r = a.sqrt().unwrap();
176    /// assert!((&r - &Matrix::from_rows(&[[5.0, 2.0], [4.0, 7.0]])).as_slice().iter().all(|x| x.abs() < 1e-13));
177    /// assert!(Matrix::from_rows(&[[-1.0, 0.0], [0.0, 1.0]]).sqrt().is_none());
178    /// ```
179    #[track_caller]
180    pub fn sqrt(&self) -> Option<Self> {
181        assert!(self.is_square(), "the matrix is not square");
182        let n = self.rows();
183        let (t, q) = self.complex_schur(|w| w.re < T::Real::ZERO)?;
184        let r = triangular_sqrt(&t, n)?;
185        Some(self.back_from_schur(&r, &q))
186    }
187
188    /// The principal logarithm `log A`, whose eigenvalues have imaginary parts in `(-π, π]`, by
189    /// inverse scaling and squaring: square roots of the Schur form `T` until it is near `I`,
190    /// then a Padé approximant of degree 7 in partial fractions, times `2^k` for `k` square
191    /// roots, as Higham's `logm` (2001) with Al-Mohy and Higham's accurate diagonal. `None`
192    /// for a singular matrix, and for a real one with an eigenvalue on the negative real axis,
193    /// whose logarithms are not real.
194    ///
195    /// # Panics
196    ///
197    /// Panics if the matrix is not square.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use a::math::matrix::Matrix;
203    ///
204    /// let a: Matrix<f64> = Matrix::from_rows(&[[1.0, 2.0], [0.5, 3.0]]);
205    /// let back = a.log().unwrap().exp();
206    /// assert!((&back - &a).as_slice().iter().all(|x| x.abs() < 1e-13));
207    /// assert!(Matrix::<f64>::zeros(2, 2).log().is_none());
208    /// ```
209    #[track_caller]
210    pub fn log(&self) -> Option<Self> {
211        assert!(self.is_square(), "the matrix is not square");
212        let n = self.rows();
213        let zero = T::Real::ZERO;
214        let (t, q) = self.complex_schur(|w| w.re <= zero)?;
215        if (0..n).any(|i| t[i + i * n] == T::C::zero()) {
216            return None;
217        }
218        // Square roots until `‖T - I‖₁ ≤ θ`.
219        let mut r = t;
220        let mut k = 0;
221        loop {
222            let mut x = r.clone();
223            for i in 0..n {
224                x[i + i * n] -= T::C::one();
225            }
226            let norm = Matrix::from_column_major(n, n, x).norm(Norm::One).to_f64();
227            if norm <= LOG_THETA || k >= 64 {
228                break;
229            }
230            r = triangular_sqrt(&r, n)?;
231            k += 1;
232        }
233        // `X = T^(1/2^k) - I`, with an accurate diagonal.
234        let mut x = r;
235        for i in 0..n {
236            x[i + i * n] = root_minus_one(q.1[i], k);
237        }
238        // `r₇(X) = Σ w_j·(I + x_j·X)⁻¹·X`.
239        let mut sum = vec![T::C::zero(); n * n];
240        for &(node, weight) in &GAUSS {
241            let node = T::Real::from_f64(node);
242            let mut m = x.clone();
243            for v in m.iter_mut() {
244                *v = v.scale(node);
245            }
246            for i in 0..n {
247                m[i + i * n] += T::C::one();
248            }
249            let mut y = x.clone();
250            trsm(
251                Side::Left,
252                Uplo::Upper,
253                Op::N,
254                false,
255                n,
256                n,
257                &m,
258                n,
259                &mut y,
260                n,
261            );
262            let w = T::Real::from_f64(weight);
263            for (s, y) in sum.iter_mut().zip(&y) {
264                *s += y.scale(w);
265            }
266        }
267        let two_k = T::Real::from_f64(2f64.powi(k));
268        for s in &mut sum {
269            *s = s.scale(two_k);
270        }
271        Some(self.back_from_schur(&sum, &q))
272    }
273
274    /// The complex Schur form `T` of the matrix and `Q`, and for later the eigenvalues as the
275    /// original diagonal of `T`; `None` for a real matrix with a real eigenvalue `excluded`
276    /// accepts.
277    #[allow(clippy::type_complexity)]
278    fn complex_schur(
279        &self,
280        excluded: impl Fn(crate::math::complex::Complex<T::Real>) -> bool,
281    ) -> Option<(Vec<T::C>, (Vec<T::C>, Vec<T::C>))> {
282        let n = self.rows();
283        if self.data.iter().any(|x| !x.is_finite()) {
284            return None;
285        }
286        let (t, q, w) = schur(self, true, true);
287        if !T::IS_COMPLEX && w.iter().any(|&w| w.im == T::Real::ZERO && excluded(w)) {
288            return None;
289        }
290        let mut tc: Vec<T::C> = t.data.iter().map(|x| x.to_c()).collect();
291        let mut qc: Vec<T::C> = q
292            .unwrap_or_default()
293            .data
294            .iter()
295            .map(|x| x.to_c())
296            .collect();
297        if !T::IS_COMPLEX {
298            real_to_complex_schur(&mut tc, &mut qc, n);
299        }
300        let diagonal = (0..n).map(|i| tc[i + i * n]).collect();
301        Some((tc, (qc, diagonal)))
302    }
303
304    /// `Q·R·Q*` in the matrix's own type: the real part for real elements.
305    fn back_from_schur(&self, r: &[T::C], q: &(Vec<T::C>, Vec<T::C>)) -> Self {
306        let n = self.rows();
307        let q = &q.0;
308        let mut qr = vec![T::C::zero(); n * n];
309        gemm(
310            n,
311            n,
312            n,
313            T::C::one(),
314            q,
315            n,
316            Op::N,
317            r,
318            n,
319            Op::N,
320            T::C::zero(),
321            &mut qr,
322            n,
323        );
324        let mut x = vec![T::C::zero(); n * n];
325        gemm(
326            n,
327            n,
328            n,
329            T::C::one(),
330            &qr,
331            n,
332            Op::N,
333            q,
334            n,
335            Op::C,
336            T::C::zero(),
337            &mut x,
338            n,
339        );
340        Matrix::from_column_major(n, n, x.into_iter().map(T::from_c).collect())
341    }
342}
343
344/// The Padé approximant `(V - U)⁻¹·(V + U)` of `e^A` with the coefficients `b`, `U` its odd part
345/// and `V` its even one, by the powers `A²`, `A⁴`, `A⁶`; for degree 13 by Higham's
346/// evaluation in six products.
347fn pade<T: Scalar>(a: &Matrix<T>, b: &[f64]) -> Matrix<T> {
348    let n = a.rows();
349    let c = |k: usize| T::from_real(T::Real::from_f64(b[k]));
350    let identity = Matrix::<T>::identity(n);
351    let a2 = a * a;
352    let (u, v) = if b.len() == 14 {
353        let a4 = &a2 * &a2;
354        let a6 = &a4 * &a2;
355        let inner_u = &(&(&a6 * c(13)) + &(&a4 * c(11))) + &(&a2 * c(9));
356        let u = &(&(&(&a6 * &inner_u) + &(&a6 * c(7))) + &(&(&a4 * c(5)) + &(&a2 * c(3))))
357            + &(&identity * c(1));
358        let inner_v = &(&(&a6 * c(12)) + &(&a4 * c(10))) + &(&a2 * c(8));
359        let v = &(&(&(&a6 * &inner_v) + &(&a6 * c(6))) + &(&(&a4 * c(4)) + &(&a2 * c(2))))
360            + &(&identity * c(0));
361        (a * &u, v)
362    } else {
363        // `Σ b_k·A^k`, the even powers by `A²` in turn.
364        let mut odd = &identity * c(1);
365        let mut even = &identity * c(0);
366        let mut power = identity.clone();
367        for k in 1..b.len() / 2 {
368            power = &power * &a2;
369            odd += &power * c(2 * k + 1);
370            even += &power * c(2 * k);
371        }
372        (a * &odd, even)
373    };
374    (&v - &u)
375        .solve(&(&v + &u))
376        .unwrap_or_else(|| Matrix::from_fn(n, n, |_, _| T::from_parts(T::Real::NAN, T::Real::NAN)))
377}
378
379/// The square root of the upper triangular `t` with the principal roots on its diagonal, by
380/// `R² = T` column by column, as Björck and Hammarling's method; `None` where `r_ii + r_jj` is
381/// zero and the equation for `r_ij` has no solution.
382fn triangular_sqrt<C: Scalar>(t: &[C], n: usize) -> Option<Vec<C>> {
383    let at = |i: usize, j: usize| i + j * n;
384    let mut r = vec![C::zero(); n * n];
385    for j in 0..n {
386        r[at(j, j)] = t[at(j, j)].sqrt();
387        for i in (0..j).rev() {
388            let mut s = t[at(i, j)];
389            for k in i + 1..j {
390                s -= r[at(i, k)] * r[at(k, j)];
391            }
392            let d = r[at(i, i)] + r[at(j, j)];
393            if d == C::zero() {
394                if s != C::zero() {
395                    return None;
396                }
397                continue;
398            }
399            r[at(i, j)] = s / d;
400        }
401    }
402    Some(r)
403}
404
405/// `a^(1/2^k) - 1` without the cancellation of the subtraction, as Al-Mohy and Higham's
406/// `sqrt_power_1`: `(a - 1) / ((1 + a^(1/2))·(1 + a^(1/4))·…·(1 + a^(1/2^k)))`.
407fn root_minus_one<C: Scalar>(a: C, k: i32) -> C {
408    if k == 0 {
409        return a - C::one();
410    }
411    let mut a = a;
412    let mut k = k;
413    // With an argument of at least π/2, `a - 1` itself cancels nothing, so one root first.
414    if a.re() < C::Real::ZERO {
415        a = a.sqrt();
416        k -= 1;
417        if k == 0 {
418            return a - C::one();
419        }
420    }
421    let z0 = a - C::one();
422    a = a.sqrt();
423    let mut r = C::one() + a;
424    for _ in 1..k {
425        a = a.sqrt();
426        r *= C::one() + a;
427    }
428    z0 / r
429}