use g_math::fixed_point::{FixedPoint, FixedVector};
use g_math::fixed_point::imperative::ode::*;
fn fp(s: &str) -> FixedPoint {
if s.starts_with('-') { -FixedPoint::from_str(&s[1..]) }
else { FixedPoint::from_str(s) }
}
fn assert_fp(got: FixedPoint, exp: FixedPoint, tol: FixedPoint, name: &str) {
let d = (got - exp).abs();
assert!(d < tol, "{}: got {}, expected {}, diff={}", name, got, exp, d);
}
struct ExponentialDecay;
impl OdeSystem for ExponentialDecay {
fn eval(&self, _t: FixedPoint, x: &FixedVector) -> FixedVector {
-x
}
}
#[test]
fn test_rk4_exponential_decay() {
let sys = ExponentialDecay;
let x0 = FixedVector::from_slice(&[fp("1")]);
let t0 = fp("0");
let t_end = fp("1");
let h = fp("0.01");
let trajectory = rk4_integrate(&sys, &x0, t0, t_end, h);
let final_point = trajectory.last().unwrap();
#[cfg(table_format = "q16_16")]
let expected = fp("0.3678");
#[cfg(table_format = "q32_32")]
let expected = fp("0.367879441");
#[cfg(table_format = "q64_64")]
let expected = fp("0.3678794411714423");
#[cfg(table_format = "q128_128")]
let expected = fp("0.36787944117144232159647396907");
#[cfg(table_format = "q256_256")]
let expected = fp("0.36787944117144232159647396907");
#[cfg(table_format = "q16_16")]
let rk4_tol = fp("0.01"); #[cfg(not(table_format = "q16_16"))]
let rk4_tol = fp("0.00001");
assert_fp(final_point.x[0], expected, rk4_tol, "exp_decay_rk4");
}
struct HarmonicOscillator;
impl OdeSystem for HarmonicOscillator {
fn eval(&self, _t: FixedPoint, x: &FixedVector) -> FixedVector {
FixedVector::from_slice(&[x[1], -x[0]])
}
}
#[test]
fn test_rk4_harmonic_oscillator() {
let sys = HarmonicOscillator;
let x0 = FixedVector::from_slice(&[fp("1"), fp("0")]); let t0 = fp("0");
let t_end = fp("3.14159265358979"); let h = fp("0.01");
let trajectory = rk4_integrate(&sys, &x0, t0, t_end, h);
let final_point = trajectory.last().unwrap();
#[cfg(table_format = "q16_16")]
let osc_tol = fp("0.1"); #[cfg(not(table_format = "q16_16"))]
let osc_tol = fp("0.001");
assert_fp(final_point.x[0], fp("-1"), osc_tol, "harmonic_x_at_pi");
assert_fp(final_point.x[1], fp("0"), osc_tol, "harmonic_v_at_pi");
}
#[test]
fn test_rk4_harmonic_energy_conservation() {
let sys = HarmonicOscillator;
let x0 = FixedVector::from_slice(&[fp("1"), fp("0")]);
let trajectory = rk4_integrate(&sys, &x0, fp("0"), fp("10"), fp("0.01"));
let (max_drift, _) = monitor_invariant(
|x| x[0] * x[0] + x[1] * x[1],
&trajectory,
);
#[cfg(table_format = "q16_16")]
let drift_tol = fp("0.1"); #[cfg(not(table_format = "q16_16"))]
let drift_tol = fp("0.001");
assert!(max_drift < drift_tol,
"Energy drift {} exceeds tolerance", max_drift);
}
#[test]
fn test_rk4_single_step_mpmath() {
let sys = ExponentialDecay;
let x0 = FixedVector::from_slice(&[fp("1")]);
let result = rk4_step(&sys, fp("0"), &x0, fp("0.1"));
assert_fp(result[0], fp("0.9048375"), fp("0.0001"), "rk4_single_step");
}
#[test]
#[cfg(not(table_format = "q16_16"))]
fn test_rk45_exponential_decay() {
let sys = ExponentialDecay;
let x0 = FixedVector::from_slice(&[fp("1")]);
let config = Rk45Config::new(fp("0.0001"), fp("0.1"));
let (trajectory, _rejected) = rk45_integrate(&sys, &x0, fp("0"), fp("1"), &config).unwrap();
let final_point = trajectory.last().unwrap();
assert_fp(final_point.x[0], fp("0.3678794411714423"), fp("0.001"), "rk45_exp_decay");
assert!(trajectory.len() > 2, "RK45 should take multiple steps");
}
#[test]
fn test_rk45_adaptive_activates() {
struct FastDecay;
impl OdeSystem for FastDecay {
fn eval(&self, _t: FixedPoint, x: &FixedVector) -> FixedVector {
FixedVector::from_slice(&[fp("-10") * x[0]])
}
}
let x0 = FixedVector::from_slice(&[fp("1")]);
let config = Rk45Config::new(fp("0.001"), fp("0.5"));
let (trajectory, _rejected) = rk45_integrate(&FastDecay, &x0, fp("0"), fp("1"), &config).unwrap();
let final_val = trajectory.last().unwrap().x[0];
assert!(final_val.abs() < fp("0.001"), "Fast decay should converge near zero");
}
struct HarmonicHamiltonian;
impl HamiltonianSystem for HarmonicHamiltonian {
fn force(&self, q: &FixedVector, _p: &FixedVector) -> FixedVector {
-q
}
fn velocity(&self, _q: &FixedVector, p: &FixedVector) -> FixedVector {
p.clone()
}
fn energy(&self, q: &FixedVector, p: &FixedVector) -> FixedPoint {
let two = FixedPoint::from_int(2);
(q.dot_precise(q) + p.dot_precise(p)) / two
}
}
#[test]
fn test_verlet_harmonic_oscillator() {
let sys = HarmonicHamiltonian;
let q0 = FixedVector::from_slice(&[fp("1")]);
let p0 = FixedVector::from_slice(&[fp("0")]);
let trajectory = verlet_integrate(&sys, &q0, &p0, fp("0"), fp("6.28318530717959"), fp("0.01"));
let final_point = trajectory.last().unwrap();
assert_fp(final_point.q[0], fp("1"), fp("0.01"), "verlet_q_period");
assert_fp(final_point.p[0], fp("0"), fp("0.01"), "verlet_p_period");
}
#[test]
fn test_verlet_energy_conservation() {
let sys = HarmonicHamiltonian;
let q0 = FixedVector::from_slice(&[fp("1")]);
let p0 = FixedVector::from_slice(&[fp("0")]);
let trajectory = verlet_integrate(&sys, &q0, &p0, fp("0"), fp("100"), fp("0.01"));
let initial_energy = trajectory[0].energy;
let mut max_drift = FixedPoint::ZERO;
for pt in &trajectory {
let drift = (pt.energy - initial_energy).abs();
if drift > max_drift { max_drift = drift; }
}
assert!(max_drift < fp("0.01"),
"Verlet energy drift {} exceeds tolerance over 10000 steps", max_drift);
}
#[test]
fn test_verlet_single_step_mpmath() {
let sys = HarmonicHamiltonian;
let q0 = FixedVector::from_slice(&[fp("1")]);
let p0 = FixedVector::from_slice(&[fp("0")]);
let (q_new, p_new) = verlet_step(&sys, &q0, &p0, fp("0.1"));
assert_fp(q_new[0], fp("0.995"), fp("0.001"), "verlet_step_q");
assert_fp(p_new[0], fp("-0.09975"), fp("0.001"), "verlet_step_p");
}
#[test]
fn test_monitor_invariant_exact() {
let sys = HarmonicOscillator;
let x0 = FixedVector::from_slice(&[fp("1"), fp("0")]);
let trajectory = rk4_integrate(&sys, &x0, fp("0"), fp("1"), fp("0.01"));
let (max_drift, drifts) = monitor_invariant(
|x| x[0] * x[0] + x[1] * x[1],
&trajectory,
);
#[cfg(table_format = "q16_16")]
let inv_tol = fp("0.01");
#[cfg(not(table_format = "q16_16"))]
let inv_tol = fp("0.0001");
assert!(max_drift < inv_tol,
"RK4 energy drift {} over 100 steps", max_drift);
assert_eq!(drifts.len(), trajectory.len());
assert!(drifts[0].is_zero(), "Initial drift should be zero");
}
#[test]
fn test_rk4_lotka_volterra() {
struct LotkaVolterra;
impl OdeSystem for LotkaVolterra {
fn eval(&self, _t: FixedPoint, x: &FixedVector) -> FixedVector {
let alpha = fp("1.5");
let beta = fp("1");
let delta = fp("1");
let gamma = fp("3");
FixedVector::from_slice(&[
alpha * x[0] - beta * x[0] * x[1],
delta * x[0] * x[1] - gamma * x[1],
])
}
}
let x0 = FixedVector::from_slice(&[fp("10"), fp("5")]);
let trajectory = rk4_integrate(&LotkaVolterra, &x0, fp("0"), fp("1"), fp("0.001"));
let final_point = trajectory.last().unwrap();
assert!(final_point.x[0] > fp("0"), "Prey population should stay positive");
assert!(final_point.x[1] > fp("0"), "Predator population should stay positive");
assert!(trajectory.len() > 100, "Should take many steps");
}
#[test]
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32")))]
fn test_rk4_order_verification() {
let sys = ExponentialDecay;
let x0 = FixedVector::from_slice(&[fp("1")]);
let exact = fp("0.3678794411714423");
let traj_coarse = rk4_integrate(&sys, &x0, fp("0"), fp("1"), fp("0.1"));
let traj_fine = rk4_integrate(&sys, &x0, fp("0"), fp("1"), fp("0.05"));
let err_coarse = (traj_coarse.last().unwrap().x[0] - exact).abs();
let err_fine = (traj_fine.last().unwrap().x[0] - exact).abs();
if !err_fine.is_zero() && !err_coarse.is_zero() {
let ratio = err_coarse / err_fine;
assert!(ratio > fp("8"), "RK4 order: error ratio {} should be > 8 (expect ~16)", ratio);
}
}