fn frob_diff<const M: usize>(a: &[[f64; M]; M], b: &[[f64; M]; M]) -> f64 {
a.iter()
.zip(b.iter())
.flat_map(|(ra, rb)| ra.iter().zip(rb.iter()))
.map(|(&x, &y)| (x - y) * (x - y))
.sum::<f64>()
.sqrt()
}
#[test]
fn exp_zero_is_identity_5x5() {
let zero = [[0.0_f64; 5]; 5];
let result = mat_exp_pade13::<f64, 5>(&zero).unwrap();
let eye = mm_eye::<f64, 5>();
assert!(
frob_diff(&result, &eye) < 1e-14,
"exp(0) != I: diff={}",
frob_diff(&result, &eye)
);
}
#[test]
fn exp_diagonal_5x5() {
let mut a = [[0.0_f64; 5]; 5];
let diag_vals = [0.0_f64, 0.5, 1.0, 1.5, 2.0];
for (i, &v) in diag_vals.iter().enumerate() {
a[i][i] = v;
}
let result = mat_exp_pade13::<f64, 5>(&a).unwrap();
for (i, &v) in diag_vals.iter().enumerate() {
let expected = v.exp();
assert!(
(result[i][i] - expected).abs() < 1e-12,
"diag[{i}]: expected {expected}, got {}",
result[i][i]
);
for (j, &val) in result[i].iter().enumerate() {
if j != i {
assert!(
val.abs() < 1e-12,
"off-diag [{i}][{j}] = {val}"
);
}
}
}
}
#[test]
fn exp_times_exp_neg_is_identity_5x5() {
let a: [[f64; 5]; 5] = [
[0.5, 0.1, 0.0, 0.0, 0.0],
[0.1, 0.5, 0.1, 0.0, 0.0],
[0.0, 0.1, 0.5, 0.1, 0.0],
[0.0, 0.0, 0.1, 0.5, 0.1],
[0.0, 0.0, 0.0, 0.1, 0.5],
];
let neg_a = mm_axpby(-1.0_f64, &a, 0.0_f64, &a); let exp_a = mat_exp_pade13::<f64, 5>(&a).unwrap();
let exp_neg_a = mat_exp_pade13::<f64, 5>(&neg_a).unwrap();
let product = mm_mul::<f64, 5>(&exp_a, &exp_neg_a);
let eye = mm_eye::<f64, 5>();
assert!(
frob_diff(&product, &eye) < 1e-12,
"exp(A)·exp(-A) not identity, diff={}",
frob_diff(&product, &eye)
);
}
#[test]
fn squarings_zero_for_small_norm() {
let mut a = [[0.0_f64; 5]; 5];
a[0][0] = 0.1;
a[1][1] = 0.05;
let s = compute_squarings::<f64, 5>(&a);
assert_eq!(s, 0, "expected 0 squarings for small norm, got {s}");
}
#[test]
fn squarings_positive_for_large_norm() {
let mut a = [[0.0_f64; 5]; 5];
a[0].fill(20.0_f64);
let s = compute_squarings::<f64, 5>(&a);
assert!(s > 0, "expected > 0 squarings for large-norm matrix, got {s}");
}
#[test]
fn mm_eye_is_identity_5x5() {
let eye = mm_eye::<f64, 5>();
for (i, row) in eye.iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(val - expected).abs() < 1e-15,
"eye[{i}][{j}] = {val}"
);
}
}
}
#[test]
fn mm_axpby_2x2() {
let a: [[f64; 2]; 2] = [[1.0, 2.0], [3.0, 4.0]];
let b: [[f64; 2]; 2] = [[5.0, 6.0], [7.0, 8.0]];
let c = mm_axpby(2.0_f64, &a, 3.0_f64, &b);
assert!((c[0][0] - 17.0).abs() < 1e-15);
assert!((c[0][1] - 22.0).abs() < 1e-15);
assert!((c[1][0] - 27.0).abs() < 1e-15);
assert!((c[1][1] - 32.0).abs() < 1e-15);
}
#[test]
fn mat_scale_entries_2x2() {
let a: [[f64; 2]; 2] = [[1.0, 2.0], [3.0, 4.0]];
let scaled = mat_scale_entries(&a, 0.5_f64);
assert!((scaled[0][0] - 0.5).abs() < 1e-15);
assert!((scaled[0][1] - 1.0).abs() < 1e-15);
assert!((scaled[1][0] - 1.5).abs() < 1e-15);
assert!((scaled[1][1] - 2.0).abs() < 1e-15);
}