mod tests {
use super::*;
use crate::{DiffusionChernoff, Grid1D, ScratchPool};
fn brownian_adj(n: usize) -> AdjointFokkerPlanckChernoff<DiffusionChernoff<f64>, f64, 1> {
let grid = Grid1D::new(-4.0_f64, 4.0, n).unwrap();
let fwd = DiffusionChernoff::new(|_| 0.5, |_| 0.0, |_| 0.0, 0.5, grid);
AdjointFokkerPlanckChernoff::new(fwd, 0.5_f64, 0.0, 0.0)
}
#[test]
fn order_inherits_from_forward() {
assert_eq!(brownian_adj(32).order(), 2);
}
#[test]
fn dirac_expands_to_four_children() {
let adj = brownian_adj(32);
let rho0 = MeasureState::<f64, 1>::dirac([0.0], 1.0);
let mut rho1 = rho0.clone();
let mut pool = ScratchPool::<f64>::new();
adj.apply_into(0.1_f64, &rho0, &mut rho1, &mut pool)
.unwrap();
assert_eq!(rho1.n_diracs(), 4);
assert!(rho1.total_variation().is_finite());
}
#[test]
fn mass_conservation_c_zero() {
let adj = brownian_adj(32);
let rho0 = MeasureState::<f64, 1>::dirac([0.0], 1.0);
let mut rho1 = rho0.clone();
let mut pool = ScratchPool::<f64>::new();
adj.apply_into(0.01_f64, &rho0, &mut rho1, &mut pool)
.unwrap();
let mass: f64 = rho1.diracs.iter().map(|(_, w)| w).sum();
assert!((mass - 1.0).abs() < 1e-14, "mass drift: {mass}");
}
#[test]
fn negative_tau_returns_err() {
let adj = brownian_adj(32);
let rho0 = MeasureState::<f64, 1>::dirac([0.0], 1.0);
let mut rho1 = rho0.clone();
let mut pool = ScratchPool::<f64>::new();
assert!(adj
.apply_into(-0.01_f64, &rho0, &mut rho1, &mut pool)
.is_err());
}
#[test]
fn total_variation_abs_weight() {
let rho = MeasureState::<f64, 1>::dirac([1.0], -2.0);
assert!((rho.total_variation() - 2.0).abs() < 1e-14);
}
#[test]
fn pair_cosine_at_origin() {
let rho = MeasureState::<f64, 1>::dirac([0.0], 1.0);
assert!((rho.pair(|pos| (1.5 * pos[0]).cos()) - 1.0).abs() < 1e-14);
}
#[test]
fn gaussian_state_variance_grows() {
let rho0 = MeasureState::<f64, 1>::gaussian([0.0], 1.0, 1.0).unwrap();
let mut rho1 = MeasureState::<f64, 1>::dirac([0.0], 0.0);
lemma_a1_push(0.1_f64, 0.5, 0.0, 0.0, &rho0, &mut rho1).unwrap();
let expected_var = 1.0 + 2.0 * 0.5 * 0.1;
assert!((rho1.gaussians[0].variance - expected_var).abs() < 1e-14);
}
#[test]
fn second_moment_dirac_formula() {
let rho = MeasureState::<f64, 1>::dirac([3.0], 2.0);
assert!((rho.second_moment() - 18.0).abs() < 1e-14);
}
#[test]
fn variance_dirac_gaussian_d2() {
use crate::state::State;
let mut rho = MeasureState::<f64, 2>::dirac([1.0, 0.0], 1.0);
rho.push_dirac_raw([3.0, 0.0], 1.0);
let gauss = MeasureState::<f64, 2>::gaussian([2.0, 0.0], 1.0, 2.0).unwrap();
rho.axpy_into(1.0, &gauss);
let tol = 1e-12_f64;
let mu = rho.first_moment();
assert!((mu[0] - 2.0).abs() < tol, "mu[0]={}", mu[0]);
assert!(mu[1].abs() < tol, "mu[1]={}", mu[1]);
let var_s = rho.variance();
assert!((var_s - 1.5).abs() < tol, "variance={var_s}");
let var_ax = rho.variance_per_axis();
assert!((var_ax[0] - 1.0).abs() < tol, "var_ax[0]={}", var_ax[0]);
assert!((var_ax[1] - 0.5).abs() < tol, "var_ax[1]={}", var_ax[1]);
assert!((var_s - (var_ax[0] + var_ax[1])).abs() < tol, "Var≠Σ_d Var_d");
}
#[allow(clippy::float_cmp)]
#[test]
fn variance_zero_mass_returns_zero() {
let rho = MeasureState::<f64, 2>::from_particles(&[]);
let mu = rho.first_moment();
assert_eq!(mu, [0.0, 0.0]);
assert_eq!(rho.variance(), 0.0);
assert_eq!(rho.variance_per_axis(), [0.0, 0.0]);
}
}