#[test]
fn sheet_1d_density_comparison() {
use crate::sim::Simulation;
use crate::tooling::core::algos::lagrangian::SemiLagrangian;
use crate::tooling::core::init::domain::{Domain, SpatialBoundType, VelocityBoundType};
use crate::tooling::core::phasespace::PhaseSpaceRepr;
use crate::tooling::core::poisson::fft::FftPoisson;
use crate::tooling::core::time::strang::StrangSplitting;
use crate::tooling::core::types::PhaseSpaceSnapshot;
let n_sheets = 16; let l_box = 8.0_f64; let g = 1.0_f64;
let amplitude = 0.1_f64; let t_final = 0.5_f64;
let rho_0 = n_sheets as f64 / (2.0 * l_box);
let omega = (4.0 * std::f64::consts::PI * g * rho_0).sqrt();
let spacing = 2.0 * l_box / n_sheets as f64;
let mut sheet_x0 = vec![0.0f64; n_sheets];
let mut sheet_v0 = vec![0.0f64; n_sheets];
for i in 0..n_sheets {
let x_eq = -l_box + (i as f64 + 0.5) * spacing;
let perturbation =
amplitude * spacing * (2.0 * std::f64::consts::PI * i as f64 / n_sheets as f64).sin();
sheet_x0[i] = x_eq + perturbation;
sheet_v0[i] = 0.0; }
let mut sheet_x_final = vec![0.0f64; n_sheets];
for i in 0..n_sheets {
let x_eq = -l_box + (i as f64 + 0.5) * spacing;
let delta = sheet_x0[i] - x_eq;
sheet_x_final[i] = x_eq + delta * (omega * t_final).cos();
}
let n_grid = 16usize;
let dx_grid = 2.0 * l_box / n_grid as f64;
let sheet_mass = 2.0 * l_box / n_sheets as f64;
let mut exact_density = vec![0.0f64; n_grid];
for &x in &sheet_x_final {
let s = (x + l_box) / dx_grid - 0.5;
let ci = s.floor() as isize;
let frac = s - ci as f64;
for di in 0..2isize {
let w = if di == 0 { 1.0 - frac } else { frac };
let idx = (ci + di).rem_euclid(n_grid as isize) as usize;
exact_density[idx] += sheet_mass * w / dx_grid;
}
}
let n = 16i128; let nv = 8i128;
let domain = Domain::builder()
.spatial_extent(l_box)
.velocity_extent(2.0)
.spatial_resolution(n)
.velocity_resolution(nv)
.t_final(t_final)
.spatial_bc(SpatialBoundType::Periodic)
.velocity_bc(VelocityBoundType::Truncated)
.build()
.unwrap();
let dx = domain.dx();
let dv = domain.dv();
let lv = domain.lv();
let nx = n as usize;
let ny = n as usize;
let nz = n as usize;
let nv1 = nv as usize;
let nv2 = nv as usize;
let nv3 = nv as usize;
let sigma_v = 0.3; let shape = [nx, ny, nz, nv1, nv2, nv3];
let n_total: usize = shape.iter().product();
let mut f = vec![0.0f64; n_total];
let norm_v = 1.0 / ((2.0 * std::f64::consts::PI).sqrt() * sigma_v).powi(3);
for ix in 0..nx {
let x1 = -l_box + (ix as f64 + 0.5) * dx[0];
let mut rho_cell = 0.0;
for &xs in &sheet_x0 {
let s = (xs + l_box) / dx[0] - 0.5;
let ci = s.floor() as isize;
let frac = s - ci as f64;
if ci == ix as isize || (ci + 1).rem_euclid(nx as isize) == ix as isize {
let di = if ci == ix as isize { 0 } else { 1 };
let w = if di == 0 { 1.0 - frac } else { frac };
rho_cell += sheet_mass * w / dx[0];
}
}
for iy in 0..ny {
for iz in 0..nz {
for iv1 in 0..nv1 {
let v1 = -lv[0] + (iv1 as f64 + 0.5) * dv[0];
for iv2 in 0..nv2 {
let v2 = -lv[1] + (iv2 as f64 + 0.5) * dv[1];
for iv3 in 0..nv3 {
let v3 = -lv[2] + (iv3 as f64 + 0.5) * dv[2];
let v2_total = v1 * v1 + v2 * v2 + v3 * v3;
let fv = norm_v * (-v2_total / (2.0 * sigma_v * sigma_v)).exp();
let f_val = rho_cell * fv / ((ny * nz) as f64);
let flat = ix * ny * nz * nv1 * nv2 * nv3
+ iy * nz * nv1 * nv2 * nv3
+ iz * nv1 * nv2 * nv3
+ iv1 * nv2 * nv3
+ iv2 * nv3
+ iv3;
f[flat] = f_val;
}
}
}
}
}
}
let snap = PhaseSpaceSnapshot {
data: f,
shape,
time: 0.0,
};
let poisson = FftPoisson::new(&domain);
let mut sim = Simulation::builder()
.domain(domain.clone())
.poisson_solver(poisson)
.advector(SemiLagrangian::new())
.integrator(StrangSplitting::new(g))
.initial_conditions(snap)
.time_final(t_final)
.build()
.unwrap();
let pkg = sim.run().unwrap();
let final_density = sim.repr.compute_density();
let [nx_f, ny_f, nz_f] = final_density.shape;
let mut vlasov_density_1d = vec![0.0f64; nx_f];
for ix in 0..nx_f {
let mut sum = 0.0;
for iy in 0..ny_f {
for iz in 0..nz_f {
sum += final_density.data[ix * ny_f * nz_f + iy * nz_f + iz];
}
}
vlasov_density_1d[ix] = sum / (ny_f * nz_f) as f64;
}
for (i, &v) in vlasov_density_1d.iter().enumerate() {
assert!(v.is_finite(), "Vlasov density[{i}] is not finite: {v}");
}
for (i, &v) in exact_density.iter().enumerate() {
assert!(v.is_finite(), "Exact density[{i}] is not finite: {v}");
}
let vlasov_mean: f64 = vlasov_density_1d.iter().sum::<f64>() / nx_f as f64;
let exact_mean: f64 = exact_density.iter().sum::<f64>() / n_grid as f64;
let vlasov_var: f64 = vlasov_density_1d
.iter()
.map(|&v| (v - vlasov_mean).powi(2))
.sum::<f64>()
/ nx_f as f64;
let exact_var: f64 = exact_density
.iter()
.map(|&v| (v - exact_mean).powi(2))
.sum::<f64>()
/ n_grid as f64;
println!(
"1D sheet model: vlasov_mean={:.4e}, exact_mean={:.4e}, vlasov_var={:.4e}, exact_var={:.4e}, steps={}",
vlasov_mean, exact_mean, vlasov_var, exact_var, pkg.total_steps
);
assert!(vlasov_mean > 0.0, "Vlasov density mean should be positive");
assert!(pkg.total_steps > 0, "Should have taken at least one step");
assert!(
!pkg.diagnostics_history
.last()
.unwrap()
.total_energy
.is_nan(),
"Final energy should not be NaN"
);
}