#[test]
#[ignore] fn plummer_perturbation_response() {
use crate::sim::Simulation;
use crate::tooling::core::algos::lagrangian::SemiLagrangian;
use crate::tooling::core::init::{
domain::{Domain, SpatialBoundType, VelocityBoundType},
isolated::{PlummerIC, sample_on_grid},
};
use crate::tooling::core::phasespace::PhaseSpaceRepr;
use crate::tooling::core::poisson::fft::FftPoisson;
use crate::tooling::core::time::strang::StrangSplitting;
let domain = Domain::builder()
.spatial_extent(8.0)
.velocity_extent(2.5)
.spatial_resolution(16)
.velocity_resolution(8)
.t_final(8.0) .spatial_bc(SpatialBoundType::Periodic)
.velocity_bc(VelocityBoundType::Truncated)
.build()
.unwrap();
let ic = PlummerIC::new(1.0, 1.0, 1.0);
let mut snap = sample_on_grid(&ic, &domain);
let epsilon = 0.05;
let dx = domain.dx();
let dv = domain.dv();
let lx = 8.0f64;
let [nx, ny, nz, nv1, nv2, nv3] = snap.shape;
for ix in 0..nx {
for iy in 0..ny {
for iz in 0..nz {
let x = -lx + (ix as f64 + 0.5) * dx[0];
let y = -lx + (iy as f64 + 0.5) * dx[1];
let z = -lx + (iz as f64 + 0.5) * dx[2];
let r = (x * x + y * y + z * z).sqrt();
let perturbation =
1.0 + epsilon * (2.0 * std::f64::consts::PI * r / (2.0 * lx)).cos();
for iv1 in 0..nv1 {
for iv2 in 0..nv2 {
for iv3 in 0..nv3 {
let idx = ix * ny * nz * nv1 * nv2 * nv3
+ iy * nz * nv1 * nv2 * nv3
+ iz * nv1 * nv2 * nv3
+ iv1 * nv2 * nv3
+ iv2 * nv3
+ iv3;
snap.data[idx] *= perturbation;
}
}
}
}
}
}
let poisson = FftPoisson::new(&domain);
let mut sim = Simulation::builder()
.domain(domain)
.poisson_solver(poisson)
.advector(SemiLagrangian::new())
.integrator(StrangSplitting::new(1.0))
.initial_conditions(snap)
.time_final(8.0)
.build()
.unwrap();
let pkg = sim.run().unwrap();
let hist = &pkg.diagnostics_history;
let e0 = hist[0].total_energy;
let e_final = hist.last().unwrap().total_energy;
let e_drift = if e0.abs() > 1e-30 {
(e_final - e0).abs() / e0.abs()
} else {
0.0
};
println!(
"Plummer perturbation: e_drift={e_drift:.4}, steps={}",
hist.len()
);
assert!(hist.len() >= 5, "Should complete multiple steps");
assert!(!e_final.is_nan(), "Energy should not be NaN");
assert!(
hist.last().unwrap().mass_in_box > 0.0,
"Mass should remain positive"
);
}