use crate::Matrix;
use crate::traits::FloatScalar;
use super::lu::LuDecomposition;
use super::LinalgError;
const PADE_COEFF: [u64; 14] = [
64764752532480000,
32382376266240000,
7771770303897600,
1187353796428800,
129060195264000,
10559470521600,
670442572800,
33522128640,
1323241920,
40840800,
960960,
16380,
182,
1,
];
const THETA_13: f64 = 5.371920351148152;
pub fn expm<T: FloatScalar, const N: usize>(
a: &Matrix<T, N, N>,
) -> Result<Matrix<T, N, N>, LinalgError> {
let norm = a.norm_one();
let theta = T::from(THETA_13).unwrap();
let mut s: u32 = 0;
if norm > theta {
let ratio = norm / theta;
let s_real = ratio.log2().ceil();
s = if s_real > T::from(63).unwrap() {
63
} else {
s_real.to_u32().unwrap_or(1)
};
if s == 0 && ratio > T::one() {
s = 1;
}
}
let scale = if s < 63 {
T::from(1u64 << s).unwrap()
} else {
T::from(2.0).unwrap().powi(s as i32)
};
let a_scaled = *a * (T::one() / scale);
let a2 = a_scaled * a_scaled;
let a4 = a2 * a2;
let a6 = a4 * a2;
let b: [T; 14] = core::array::from_fn(|i| T::from(PADE_COEFF[i]).unwrap());
let ident = Matrix::<T, N, N>::eye();
let u_inner = a6 * (a6 * b[13] + a4 * b[11] + a2 * b[9])
+ a6 * b[7]
+ a4 * b[5]
+ a2 * b[3]
+ ident * b[1];
let u = a_scaled * u_inner;
let v = a6 * (a6 * b[12] + a4 * b[10] + a2 * b[8])
+ a6 * b[6]
+ a4 * b[4]
+ a2 * b[2]
+ ident * b[0];
let p = u + v;
let q = v - u;
let lu = LuDecomposition::new(&q)?;
let mut result = lu.inverse() * p;
for _ in 0..s {
result = result * result;
}
Ok(result)
}
impl<T: FloatScalar, const N: usize> Matrix<T, N, N> {
pub fn expm(&self) -> Result<Self, LinalgError> {
expm(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Matrix;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
fn mat_approx_eq<const N: usize>(
a: &Matrix<f64, N, N>,
b: &Matrix<f64, N, N>,
tol: f64,
) -> bool {
for i in 0..N {
for j in 0..N {
if !approx_eq(a[(i, j)], b[(i, j)], tol) {
return false;
}
}
}
true
}
#[test]
fn expm_zero() {
let zero = Matrix::<f64, 3, 3>::zeros();
let result = expm(&zero).unwrap();
let eye = Matrix::<f64, 3, 3>::eye();
assert!(mat_approx_eq(&result, &eye, 1e-12));
}
#[test]
fn expm_identity() {
let eye = Matrix::<f64, 3, 3>::eye();
let result = expm(&eye).unwrap();
let e = core::f64::consts::E;
let expected = Matrix::new([[e, 0.0, 0.0], [0.0, e, 0.0], [0.0, 0.0, e]]);
assert!(mat_approx_eq(&result, &expected, 1e-10));
}
#[test]
fn expm_diagonal() {
let a = 2.0_f64;
let b = -1.0_f64;
let m = Matrix::new([[a, 0.0], [0.0, b]]);
let result = expm(&m).unwrap();
let expected = Matrix::new([[a.exp(), 0.0], [0.0, b.exp()]]);
assert!(mat_approx_eq(&result, &expected, 1e-10));
}
#[test]
fn expm_nilpotent() {
let a = Matrix::new([[0.0_f64, 1.0], [0.0, 0.0]]);
let result = expm(&a).unwrap();
let expected = Matrix::new([[1.0, 1.0], [0.0, 1.0]]);
assert!(mat_approx_eq(&result, &expected, 1e-12));
}
#[test]
fn expm_antisymmetric() {
let theta = 0.7_f64;
let a = Matrix::new([[0.0, -theta], [theta, 0.0]]);
let result = expm(&a).unwrap();
let expected = Matrix::new([
[theta.cos(), -theta.sin()],
[theta.sin(), theta.cos()],
]);
assert!(mat_approx_eq(&result, &expected, 1e-12));
}
#[test]
fn expm_known_3x3() {
let a = Matrix::new([
[1.0_f64, 1.0, 0.0],
[0.0, 0.0, 2.0],
[0.0, 0.0, -1.0],
]);
let result = expm(&a).unwrap();
let e = core::f64::consts::E;
assert!(approx_eq(result[(0, 0)], e, 1e-10));
assert!(approx_eq(result[(1, 1)], 1.0, 1e-10));
assert!(approx_eq(result[(2, 2)], 1.0 / e, 1e-10));
assert!(approx_eq(result[(0, 1)], e - 1.0, 1e-10));
assert!(approx_eq(result[(1, 2)], 2.0 * (1.0 - 1.0 / e), 1e-10));
assert!(approx_eq(result[(1, 0)], 0.0, 1e-10));
assert!(approx_eq(result[(2, 0)], 0.0, 1e-10));
assert!(approx_eq(result[(2, 1)], 0.0, 1e-10));
}
#[test]
fn expm_scaling() {
let a = Matrix::new([
[0.1_f64, 0.2, -0.1],
[-0.3, 0.0, 0.15],
[0.05, -0.1, 0.2],
]);
let t1 = 1.5_f64;
let t2 = 2.3_f64;
let exp_t1 = expm(&(a * t1)).unwrap();
let exp_t2 = expm(&(a * t2)).unwrap();
let exp_sum = expm(&(a * (t1 + t2))).unwrap();
let product = exp_t1 * exp_t2;
assert!(mat_approx_eq(&exp_sum, &product, 1e-10));
}
#[test]
fn expm_f32() {
let a = Matrix::new([[0.0_f32, -1.0], [1.0, 0.0]]);
let result = expm(&a).unwrap();
let c = 1.0_f32.cos();
let s = 1.0_f32.sin();
let expected = Matrix::new([[c, -s], [s, c]]);
for i in 0..2 {
for j in 0..2 {
assert!((result[(i, j)] - expected[(i, j)]).abs() < 1e-4);
}
}
}
}