use semiflow::{
ApproximationSubspace, ChernoffFunction, Grid1D, MatrixDiffusionChernoff, MatrixGridFn1D,
ScratchPool, State,
};
fn make_grid(n: usize) -> Grid1D<f64> {
Grid1D::new(-5.0, 5.0, n).unwrap()
}
#[test]
fn matrix_grid_fn_new_zeroed() {
let g = make_grid(8);
let state = MatrixGridFn1D::<f64, 2>::new(g);
assert!(state.values.iter().all(|&v| v == 0.0));
assert_eq!(state.len(), 8);
}
#[test]
fn matrix_grid_fn_from_fn_point_view() {
let g = make_grid(4);
let state = MatrixGridFn1D::<f64, 2>::from_fn(g, |x| [x, x * x]);
let v = state.point_view(1);
let x1 = make_grid(4).x_at(1);
assert!((v[0] - x1).abs() < 1e-12);
assert!((v[1] - x1 * x1).abs() < 1e-12);
}
#[test]
fn state_axpy_into_scale_norm() {
let g = make_grid(4);
let mut u = MatrixGridFn1D::<f64, 2>::from_fn(g, |_| [1.0, 2.0]);
let v = MatrixGridFn1D::<f64, 2>::from_fn(g, |_| [3.0, 4.0]);
u.axpy_into(2.0, &v);
assert!((u.values[0] - 7.0).abs() < 1e-12); assert!((u.values[1] - 10.0).abs() < 1e-12); let norm = u.norm_sup();
assert!((norm - 10.0).abs() < 1e-12);
}
#[test]
fn matrix_exp_identity_via_zero_c() {
let g = make_grid(8);
let kernel =
MatrixDiffusionChernoff::<f64, 2>::new(|_, _| {}, |_, _| {}, |_, _| {}, g).unwrap();
let u0 = MatrixGridFn1D::<f64, 2>::from_fn(g, |x| [x.sin(), x.cos()]);
let mut u1 = MatrixGridFn1D::<f64, 2>::new(g);
let mut pool = ScratchPool::<f64>::new();
kernel.apply_into(0.0, &u0, &mut u1, &mut pool).unwrap();
for k in 0..g.n {
let v0 = u0.point_view(k);
let v1 = u1.point_view(k);
assert!(
(v0[0] - v1[0]).abs() < 1e-12,
"component 0 mismatch at k={k}"
);
assert!(
(v0[1] - v1[1]).abs() < 1e-12,
"component 1 mismatch at k={k}"
);
}
}
#[test]
fn apply_into_m2_smoke_finite() {
let g = make_grid(8);
let kernel = MatrixDiffusionChernoff::<f64, 2>::new(
|_, a| {
a[0][0] = 1.0;
a[1][1] = 0.5;
},
|_, _| {},
|_, _| {},
g,
)
.unwrap();
let u0 = MatrixGridFn1D::<f64, 2>::from_fn(g, |x| [(-x * x).exp(), (-x * x / 2.0).exp()]);
let mut u1 = MatrixGridFn1D::<f64, 2>::new(g);
let mut pool = ScratchPool::<f64>::new();
kernel.apply_into(0.01, &u0, &mut u1, &mut pool).unwrap();
assert!(
u1.values.iter().all(|v| v.is_finite()),
"apply_into M=2 non-finite"
);
}
#[test]
fn in_subspace_gaussian_ic() {
let g = make_grid(8);
let kernel = MatrixDiffusionChernoff::<f64, 2>::new(
|_, a| {
a[0][0] = 1.0;
a[1][1] = 1.0;
},
|_, _| {},
|_, _| {},
g,
)
.unwrap();
let u0 = MatrixGridFn1D::<f64, 2>::from_fn(g, |x| [(-x * x).exp(); 2]);
assert!(kernel.in_subspace(&u0));
}
#[test]
fn apply_into_m5_pade_smoke() {
let g = make_grid(8);
let kernel =
MatrixDiffusionChernoff::<f64, 5>::new(|_, _| {}, |_, _| {}, |_, _| {}, g).unwrap();
let u0 = MatrixGridFn1D::<f64, 5>::new(g);
let mut u1 = MatrixGridFn1D::<f64, 5>::new(g);
let mut pool = ScratchPool::<f64>::new();
let result = kernel.apply_into(0.01, &u0, &mut u1, &mut pool);
assert!(result.is_ok(), "M=5 Pade path must not error: {result:?}");
assert!(
u1.values.iter().all(|v| v.is_finite()),
"M=5 Pade output must be finite"
);
}
#[test]
fn apply_into_m1_scalar_matches_scalar_exp() {
let g = make_grid(8);
let c_val = 0.5_f64;
let tau = 0.1_f64;
let kernel = MatrixDiffusionChernoff::<f64, 1>::new(
|_, a| {
a[0][0] = 1.0;
},
|_, _| {},
move |_, c| {
c[0][0] = c_val;
},
g,
)
.unwrap();
let u0 = MatrixGridFn1D::<f64, 1>::from_fn(g, |_| [1.0]);
let mut u1 = MatrixGridFn1D::<f64, 1>::new(g);
let mut pool = ScratchPool::<f64>::new();
kernel.apply_into(tau, &u0, &mut u1, &mut pool).unwrap();
let expected = (tau * c_val).exp();
for k in 0..g.n {
let v = u1.point_view(k);
assert!(
(v[0] - expected).abs() < 1e-10,
"k={k}: got {}, expected {expected}",
v[0]
);
}
}