#[cfg(not(feature = "parallel"))]
use semiflow::nonseparable2d::NonSeparable2DChernoff;
#[cfg(not(feature = "parallel"))]
use semiflow::nonseparable2d_aniso::NonSeparable2DAnisotropicChernoff;
#[cfg(not(feature = "parallel"))]
use semiflow::strang2d::Strang2D;
#[cfg(not(feature = "parallel"))]
use semiflow::strang3d::Strang3D;
use semiflow::{
adaptive::AdaptivePI,
axis::{Axis, AxisLift},
boundary::InterpKind,
diffusion::DiffusionChernoff,
diffusion4::Diffusion4thChernoff,
diffusion6::Diffusion6thChernoff,
drift_reaction::DriftReactionChernoff,
grid2d::Grid2D,
grid3d::Grid3D,
grid_fn2d::GridFn2D,
grid_fn3d::GridFn3D,
shift1d::ShiftChernoff1D,
strang::StrangSplit,
truncated_exp::TruncatedExpDiffusionChernoff,
truncated_exp4::TruncatedExp4thDiffusionChernoff,
BoundaryPolicy, Grid1D, GridFn1D, SemiflowFloat, State,
};
fn assert_abs_f32(actual: f32, expected: f32, tol: f32, label: &str) {
let diff = (actual - expected).abs();
assert!(
diff <= tol,
"{label}: |{actual} - {expected}| = {diff} > tol {tol}"
);
}
use semiflow::{
chernoff::{ApplyChernoffExt, ChernoffFunction, Growth},
error::SemiflowError,
scratch::ScratchPool,
};
#[derive(Clone)]
struct WrapDiff<F: SemiflowFloat>(semiflow::diffusion::DiffusionChernoff<F>);
impl<F: SemiflowFloat> ChernoffFunction<F> for WrapDiff<F> {
type S = GridFn1D<F>;
fn apply_into(
&self,
tau: F,
src: &GridFn1D<F>,
dst: &mut GridFn1D<F>,
_scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
let out = self.0.apply_f(tau, src)?;
dst.values.clear();
dst.values.extend_from_slice(&out.values);
Ok(())
}
fn order(&self) -> u32 {
self.0.order_val()
}
fn growth(&self) -> Growth<F> {
Growth::contraction()
}
}
#[derive(Clone)]
struct WrapDrift<F: SemiflowFloat>(semiflow::drift_reaction::DriftReactionChernoff<F>);
impl<F: SemiflowFloat> ChernoffFunction<F> for WrapDrift<F> {
type S = GridFn1D<F>;
fn apply_into(
&self,
tau: F,
src: &GridFn1D<F>,
dst: &mut GridFn1D<F>,
_scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
let out = self.0.apply_f(tau, src)?;
dst.values.clear();
dst.values.extend_from_slice(&out.values);
Ok(())
}
fn order(&self) -> u32 {
self.0.order_val()
}
fn growth(&self) -> Growth<F> {
Growth::contraction()
}
}
#[test]
fn grid1d_f32_new_generic() {
let grid =
Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 100).expect("Grid1D::<f32>::new_generic");
assert_eq!(grid.n, 100);
assert_abs_f32(grid.xmin, -5.0_f32, 1e-7, "xmin");
assert_abs_f32(grid.xmax, 5.0_f32, 1e-7, "xmax");
let dx = grid.dx();
let expected_dx = 10.0_f32 / 99.0_f32;
assert_abs_f32(dx, expected_dx, 1e-6, "dx");
assert_abs_f32(grid.x_at(0), -5.0_f32, 1e-7, "x_at(0)");
assert_abs_f32(grid.x_at(99), 5.0_f32, 1e-6, "x_at(n-1)");
}
#[test]
fn grid1d_f32_builder_methods() {
let grid = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 50)
.expect("grid")
.with_boundary(BoundaryPolicy::ZeroExtend);
assert_eq!(grid.n, 50);
assert!(matches!(grid.boundary, BoundaryPolicy::ZeroExtend));
}
#[test]
fn grid_fn1d_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 101).expect("grid");
let gfn = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let mid = gfn.values[50];
assert_abs_f32(mid, 1.0_f32, 1e-5, "g(0) ≈ 1");
assert!(gfn.values[0] < 1e-9_f32, "g(-5) near zero");
assert!(gfn.values[100] < 1e-9_f32, "g(5) near zero");
}
#[test]
fn grid_fn1d_f32_sample_generic() {
let grid = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 101).expect("grid");
let gfn = GridFn1D::from_fn_generic(grid, |x: f32| x * x);
let v = gfn.sample_generic(0.0_f32).expect("sample_generic");
assert_abs_f32(v, 0.0_f32, 1e-4, "x²(0)=0");
let v2 = gfn.sample_generic(0.5_f32).expect("sample_generic");
assert_abs_f32(v2, 0.25_f32, 1e-4, "x²(0.5)=0.25");
}
#[test]
fn diffusion_f32_apply_f_runs() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 51).expect("grid");
let dc = DiffusionChernoff::<f32>::new(
|_: f32| 1.0_f32,
|_: f32| 0.0_f32,
|_: f32| 0.0_f32,
1.0_f64, grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let tau = 0.01_f32;
let f1 = dc.apply_f(tau, &f0).expect("apply_f");
let norm0: f32 = f0.values.iter().copied().fold(0.0_f32, f32::max);
let norm1: f32 = f1
.values
.iter()
.copied()
.map(f32::abs)
.fold(0.0_f32, f32::max);
assert!(
norm1 <= norm0 * 1.05_f32,
"contractivity violated: norm1={norm1} > norm0={norm0}*1.05"
);
assert_eq!(f1.values.len(), f0.values.len());
}
#[test]
fn diffusion_f32_matches_f64_within_tol() {
let grid64 = Grid1D::new(-5.0_f64, 5.0_f64, 51)
.expect("grid64")
.with_interp(InterpKind::CubicHermite);
let dc64 = DiffusionChernoff::<f64>::new(
|_: f64| 1.0_f64,
|_: f64| 0.0_f64,
|_: f64| 0.0_f64,
1.0_f64,
grid64,
);
let f0_64 = GridFn1D::from_fn(grid64, |x: f64| (-x * x).exp());
let f1_64 = dc64.apply_f(0.01_f64, &f0_64).expect("apply_f f64");
let grid32 = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 51)
.expect("grid32")
.with_interp(InterpKind::CubicHermite);
let dc32 = DiffusionChernoff::<f32>::new(
|_: f32| 1.0_f32,
|_: f32| 0.0_f32,
|_: f32| 0.0_f32,
1.0_f64,
grid32,
);
let f0_32 = GridFn1D::from_fn_generic(grid32, |x: f32| (-x * x).exp());
let f1_32 = dc32.apply_f(0.01_f32, &f0_32).expect("apply_f f32");
assert_eq!(f1_64.values.len(), f1_32.values.len());
let tol = 5e-3_f64;
for (i, (&v64, &v32)) in f1_64.values.iter().zip(f1_32.values.iter()).enumerate() {
let v32_as_f64 = f64::from(v32);
let diff = (v64 - v32_as_f64).abs();
let scale = v64.abs().max(1e-10); assert!(
diff / scale <= tol,
"index {i}: f64={v64:.8e} f32={v32:.8e} rel_diff={:.2e} > tol {tol}",
diff / scale
);
}
}
fn compute_sup_norm<F: SemiflowFloat>(values: &[F]) -> F {
values.iter().fold(F::zero(), |acc, &v| {
let abs_v = <F as num_traits::Float>::abs(v);
if abs_v > acc {
abs_v
} else {
acc
}
})
}
#[test]
fn remote_float_bound_f32() {
let v: Vec<f32> = vec![0.1, -0.5, 0.3, -1.2_f32];
let norm = compute_sup_norm(&v);
assert_abs_f32(norm, 1.2_f32, 1e-7, "sup norm f32");
}
#[test]
fn remote_float_bound_f64() {
let v: Vec<f64> = vec![0.1, -0.5, 0.3, -1.2_f64];
let norm = compute_sup_norm(&v);
let diff = (norm - 1.2_f64).abs();
assert!(diff <= 1e-14_f64, "sup norm f64: |{norm} - 1.2| = {diff}");
}
#[test]
fn diffusion4_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 51).expect("grid");
let dc = Diffusion4thChernoff::<f32>::new_generic(
|_: f32| 1.0_f32, |_: f32| 0.0_f32, |_: f32| 0.0_f32, 1.0_f64, grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let dx = grid.dx();
let tau = 0.3_f32 * dx * dx;
let f1 = dc.apply_f(tau, &f0).expect("apply_f D4 f32");
assert_eq!(f1.values.len(), f0.values.len());
let norm0 = compute_sup_norm(&f0.values);
let norm1 = compute_sup_norm(&f1.values);
assert!(
norm1 <= norm0 * 1.1_f32,
"D4 f32: norm1={norm1} > 1.1 * norm0={norm0}"
);
}
#[test]
fn diffusion6_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 51).expect("grid");
let dc = Diffusion6thChernoff::<f32>::new_generic(
|_: f32| 1.0_f32,
|_: f32| 0.0_f32,
|_: f32| 0.0_f32,
1.0_f64,
grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let dx = grid.dx();
let tau = 0.1_f32 * dx * dx;
let f1 = dc.apply_f(tau, &f0).expect("apply_f D6 f32");
assert_eq!(f1.values.len(), f0.values.len());
let norm0 = compute_sup_norm(&f0.values);
let norm1 = compute_sup_norm(&f1.values);
assert!(
norm1 <= norm0 * 1.1_f32,
"D6 f32: norm1={norm1} > 1.1 * norm0={norm0}"
);
}
#[test]
fn truncated_exp_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 51).expect("grid");
let dc = TruncatedExpDiffusionChernoff::<f32>::new_generic(
|_: f32| 1.0_f32,
|_: f32| 0.0_f32,
|_: f32| 0.0_f32,
1.0_f64,
grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let dx = grid.dx();
let tau = 0.4_f32 * dx * dx;
let f1 = dc.apply_f(tau, &f0).expect("apply_f TExp f32");
assert_eq!(f1.values.len(), f0.values.len());
let norm0 = compute_sup_norm(&f0.values);
let norm1 = compute_sup_norm(&f1.values);
assert!(
norm1 <= norm0 * 1.1_f32,
"TExp f32: norm1={norm1} > 1.1 * norm0={norm0}"
);
}
#[test]
fn truncated_exp4_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 51).expect("grid");
let dc = TruncatedExp4thDiffusionChernoff::<f32>::new_generic(
|_: f32| 1.0_f32,
|_: f32| 0.0_f32,
|_: f32| 0.0_f32,
1.0_f64,
grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let dx = grid.dx();
let tau = 0.3_f32 * dx * dx;
let f1 = dc.apply_f(tau, &f0).expect("apply_f TExp4 f32");
assert_eq!(f1.values.len(), f0.values.len());
let norm0 = compute_sup_norm(&f0.values);
let norm1 = compute_sup_norm(&f1.values);
assert!(
norm1 <= norm0 * 1.1_f32,
"TExp4 f32: norm1={norm1} > 1.1 * norm0={norm0}"
);
}
#[test]
fn shift_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-5.0_f32, 5.0_f32, 101).expect("grid");
let sc = ShiftChernoff1D::<f32>::new_generic(
|_: f32| 0.01_f32, |_: f32| 0.0_f32, |_: f32| 0.0_f32, 0.0_f64, grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let tau = 0.1_f32;
let f1 = sc.apply_f(tau, &f0).expect("apply_f Shift f32");
assert_eq!(f1.values.len(), f0.values.len());
for (i, &v) in f1.values.iter().enumerate() {
assert!(v >= -1e-5_f32, "Shift f32: negative at i={i}: v={v}");
}
}
#[test]
fn drift_reaction_f32_smoke() {
let grid = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 61).expect("grid");
let dr = DriftReactionChernoff::<f32>::new_generic(
|_: f32| 0.0_f32, |_: f32| 0.0_f32, 0.0_f64, grid,
);
let f0 = GridFn1D::from_fn_generic(grid, |x: f32| (-x * x).exp());
let tau = 1e-4_f32;
let f1 = dr.apply_f(tau, &f0).expect("apply_f DriftReaction f32");
assert_eq!(f1.values.len(), f0.values.len());
let max_diff = f0
.values
.iter()
.zip(f1.values.iter())
.map(|(&a, &b)| (a - b).abs())
.fold(0.0_f32, f32::max);
assert!(
max_diff < 1e-3_f32,
"DriftReaction f32: max diff with b=c=0 is {max_diff} at tau={tau}"
);
}
#[test]
fn adaptive_pi_f32_smoke() {
let grid = Grid1D::new(-3.0_f64, 3.0_f64, 41).expect("grid");
let dc = DiffusionChernoff::<f64>::new(
|_: f64| 0.5_f64,
|_: f64| 0.0_f64,
|_: f64| 0.0_f64,
0.5_f64,
grid,
);
let mut pi = AdaptivePI::new(dc);
let f0 = GridFn1D::from_fn(grid, |x: f64| (-x * x).exp());
let outcome = pi.evolve_adaptive(0.05_f64, &f0).expect("evolve_adaptive");
let f1 = outcome.final_state;
assert_eq!(f1.values.len(), f0.values.len());
let norm0: f64 = f0.values.iter().copied().fold(0.0_f64, f64::max);
let norm1: f64 = f1
.values
.iter()
.copied()
.map(f64::abs)
.fold(0.0_f64, f64::max);
assert!(
norm1 <= norm0 * 1.05_f64,
"AdaptivePI f64: norm1={norm1} > norm0={norm0}*1.05"
);
}
#[test]
fn grid2d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).expect("gx");
let gy = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 6).expect("gy");
let g = Grid2D::<f32>::new(gx, gy);
assert_eq!(g.nx(), 8);
assert_eq!(g.ny(), 6);
assert_eq!(g.len(), 48);
let ax = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).expect("ax");
let ay = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 6).expect("ay");
let clone = Grid2D::<f32>::new(ax, ay);
assert_eq!(g, clone, "Grid2D<f32> PartialEq");
}
#[test]
fn grid_fn2d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).expect("gx");
let gy = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 6).expect("gy");
let g = Grid2D::<f32>::new(gx, gy);
let f = GridFn2D::<f32>::from_fn_generic(g, |x, y| (-x * x - y * y).exp());
assert_eq!(f.values.len(), 48);
for &v in &f.values {
assert!(v > 0.0_f32 && v <= 1.0_f32, "value out of range: {v}");
}
let ones = GridFn2D::<f32>::from_fn_generic(g, |_, _| 1.0_f32);
let mut out = GridFn2D::<f32>::from_fn_generic(g, |_, _| 2.0_f32);
out.axpy(3.0_f32, &ones);
assert_abs_f32(out.norm_sup(), 5.0_f32, 1e-6, "axpy norm_sup");
out.scale(2.0_f32);
assert_abs_f32(out.norm_sup(), 10.0_f32, 1e-5, "scale norm_sup");
let z = out.zeroed_like();
assert_abs_f32(z.norm_sup(), 0.0_f32, f32::EPSILON, "zeroed_like");
let row0 = f.row_generic(0);
assert_eq!(row0.values.len(), 8);
let col0 = f.col_generic(0);
assert_eq!(col0.values.len(), 6);
let err = GridFn2D::<f32>::new_generic(g, vec![0.0_f32; 3]).unwrap_err();
assert!(
matches!(err, semiflow::SemiflowError::DomainViolation { .. }),
"expected DomainViolation, got {err:?}"
);
}
#[test]
fn grid3d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).expect("gx");
let gy = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 6).expect("gy");
let gz = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 4).expect("gz");
let g = Grid3D::<f32>::new_generic(gx, gy, gz).expect("Grid3D f32");
assert_eq!(g.nx(), 8);
assert_eq!(g.ny(), 6);
assert_eq!(g.nz(), 4);
assert_eq!(g.len(), 8 * 6 * 4);
let ax = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).expect("ax");
let ay = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 6).expect("ay");
let az = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 4).expect("az");
let clone = Grid3D::<f32>::new_generic(ax, ay, az).expect("clone");
assert_eq!(g, clone, "Grid3D<f32> PartialEq");
}
#[test]
fn grid_fn3d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).expect("gx");
let gy = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 6).expect("gy");
let gz = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 4).expect("gz");
let g = Grid3D::<f32>::new_generic(gx, gy, gz).expect("Grid3D f32");
let f = GridFn3D::<f32>::from_fn_generic(g, |x, y, z| (-x * x - y * y - z * z).exp());
assert_eq!(f.values.len(), 8 * 6 * 4);
for &v in &f.values {
assert!(v > 0.0_f32 && v <= 1.0_f32, "value out of range: {v}");
}
let ones = GridFn3D::<f32>::from_fn_generic(g, |_, _, _| 1.0_f32);
let mut out = GridFn3D::<f32>::from_fn_generic(g, |_, _, _| 2.0_f32);
out.axpy(3.0_f32, &ones);
assert_abs_f32(out.norm_sup(), 5.0_f32, 1e-6, "axpy norm_sup 3D");
out.scale(2.0_f32);
assert_abs_f32(out.norm_sup(), 10.0_f32, 1e-5, "scale norm_sup 3D");
let z = out.zeroed_like();
assert_abs_f32(z.norm_sup(), 0.0_f32, f32::EPSILON, "zeroed_like 3D");
let err = GridFn3D::<f32>::new_generic(g, vec![0.0_f32; 3]).unwrap_err();
assert!(
matches!(err, semiflow::SemiflowError::DomainViolation { .. }),
"expected DomainViolation, got {err:?}"
);
}
#[test]
fn axis_lift_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 16).unwrap();
let gy = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 12).unwrap();
let g2 = Grid2D::<f32>::new(gx, gy);
let f = GridFn2D::<f32>::from_fn_generic(g2, |x, _y| (-x * x).exp());
let diff = DiffusionChernoff::<f32>::new(|_| 0.5_f32, |_| 0.0_f32, |_| 0.0_f32, 0.5, gx);
let lift = AxisLift::<_, f32>::new(WrapDiff(diff), Axis::X);
let out = lift.apply_chernoff(0.01_f32, &f).unwrap();
assert_eq!(out.values.len(), f.values.len());
assert!(out.values.iter().all(|v| v.is_finite()));
}
#[cfg(not(feature = "parallel"))]
#[test]
fn strang2d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 12).unwrap();
let gy = Grid1D::<f32>::new_generic(-3.0_f32, 3.0_f32, 10).unwrap();
let g2 = Grid2D::<f32>::new(gx, gy);
let f = GridFn2D::<f32>::from_fn_generic(g2, |x, y| (-x * x - y * y).exp());
let dx = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.25_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.25,
gx,
));
let dy = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.25_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.25,
gy,
));
let s = Strang2D::<_, _, f32>::new(dx, dy);
let out = s.apply_chernoff(0.01_f32, &f).unwrap();
assert_eq!(out.values.len(), f.values.len());
assert!(out.values.iter().all(|v| v.is_finite()));
assert_eq!(s.order(), 2);
}
#[cfg(not(feature = "parallel"))]
#[test]
fn nonseparable2d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).unwrap();
let gy = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).unwrap();
let g2 = Grid2D::<f32>::new(gx, gy);
let f = GridFn2D::<f32>::from_fn_generic(g2, |x, _y| x * 0.5_f32);
let ix = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.5_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.5,
gx,
));
let iy = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.5_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.5,
gy,
));
let op = NonSeparable2DChernoff::<_, _, f32>::new(ix, iy, |_, _| 0.0_f32, 0.0, g2).unwrap();
let out = op.apply_chernoff(0.005_f32, &f).unwrap();
assert_eq!(out.values.len(), f.values.len());
assert!(out.values.iter().all(|v| v.is_finite()));
assert_eq!(op.order(), 2);
}
#[cfg(not(feature = "parallel"))]
#[test]
fn nonseparable2d_aniso_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).unwrap();
let gy = Grid1D::<f32>::new_generic(-1.0_f32, 1.0_f32, 8).unwrap();
let g2 = Grid2D::<f32>::new(gx, gy);
let f = GridFn2D::<f32>::from_fn_generic(g2, |x, _y| x * 0.5_f32);
let ix = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.5_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.5,
gx,
));
let iy = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.5_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.5,
gy,
));
let op = NonSeparable2DAnisotropicChernoff::<_, _, f32>::new(ix, iy, |_, _| 0.0_f32, 0.0, g2)
.unwrap();
let out = op.apply_chernoff(0.005_f32, &f).unwrap();
assert_eq!(out.values.len(), f.values.len());
assert!(out.values.iter().all(|v| v.is_finite()));
assert_eq!(op.order(), 2);
}
#[cfg(not(feature = "parallel"))]
#[test]
fn strang3d_f32_smoke() {
let gx = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 8).unwrap();
let gy = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 6).unwrap();
let gz = Grid1D::<f32>::new_generic(-2.0_f32, 2.0_f32, 6).unwrap();
let g3 = Grid3D::<f32>::new_generic(gx, gy, gz).unwrap();
let f = GridFn3D::<f32>::from_fn_generic(g3, |x, y, z| (-x * x - y * y - z * z).exp());
let dx = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.25_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.25,
gx,
));
let dy = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.25_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.25,
gy,
));
let dz = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.25_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.25,
gz,
));
let s = Strang3D::<_, _, _, f32>::new(dx, dy, dz);
let out = s.apply_chernoff(0.005_f32, &f).unwrap();
assert_eq!(out.values.len(), f.values.len());
assert!(out.values.iter().all(|v| v.is_finite()));
assert_eq!(s.order(), 2);
}
#[test]
fn strang_split_f32_smoke() {
let g = Grid1D::<f32>::new_generic(-4.0_f32, 4.0_f32, 20).unwrap();
let f = GridFn1D::<f32>::from_fn_generic(g, |x| (-x * x).exp());
let diff = WrapDiff(DiffusionChernoff::<f32>::new(
|_| 0.5_f32,
|_| 0.0_f32,
|_| 0.0_f32,
0.5,
g,
));
let drift = WrapDrift(DriftReactionChernoff::<f32>::new_generic(
|_| 0.1_f32,
|_| 0.0_f32,
0.0,
g,
));
let s = StrangSplit::<_, _, f32>::new(diff, drift);
let out = s.apply_chernoff(0.01_f32, &f).unwrap();
assert_eq!(out.values.len(), f.values.len());
assert!(out.values.iter().all(|v| v.is_finite()));
assert_eq!(s.order(), 2);
}
#[test]
fn adaptive_pi_strang_split_f64_smoke() {
use semiflow::grid::Grid1D as G1;
let g = G1::new(-4.0_f64, 4.0_f64, 32).unwrap();
let f = GridFn1D::<f64>::from_fn(g, |x| (-x * x).exp());
let diff = DiffusionChernoff::new(|_| 0.5_f64, |_| 0.0_f64, |_| 0.0_f64, 0.5, g);
let drift = DriftReactionChernoff::new(|_| 0.0_f64, |_| 0.0_f64, 0.0, g);
let s = StrangSplit::new(diff, drift);
let mut pi = AdaptivePI::new(s);
let outcome = pi.evolve_adaptive(0.1, &f).unwrap();
assert!(outcome.final_state.values.iter().all(|v| v.is_finite()));
assert!(outcome.steps_accepted > 0);
}