use crate::CelestialBody;
use crate::gravity::nbody::compute_accelerations;
pub enum Integrator {
LeapfrogKDK,
VelocityVerlet,
Yoshida4,
}
impl Integrator {
pub fn step(&self, bodies: &mut [CelestialBody], dt: f64) {
match self {
Integrator::LeapfrogKDK => leapfrog_kdk(bodies, dt),
Integrator::VelocityVerlet => velocity_verlet(bodies, dt),
Integrator::Yoshida4 => yoshida4(bodies, dt),
}
}
}
pub fn leapfrog_kdk(bodies: &mut [CelestialBody], dt: f64) {
for b in bodies.iter_mut() {
b.velocity += b.acceleration * (0.5 * dt);
}
for b in bodies.iter_mut() {
b.position += b.velocity * dt;
}
compute_accelerations(bodies);
for b in bodies.iter_mut() {
b.velocity += b.acceleration * (0.5 * dt);
}
}
pub fn velocity_verlet(bodies: &mut [CelestialBody], dt: f64) {
for b in bodies.iter_mut() {
b.position += b.velocity * dt + b.acceleration * (0.5 * dt * dt);
}
let old_acc: Vec<_> = bodies.iter().map(|b| b.acceleration).collect();
compute_accelerations(bodies);
for (b, a_old) in bodies.iter_mut().zip(old_acc.iter()) {
b.velocity += (*a_old + b.acceleration) * (0.5 * dt);
}
}
pub fn yoshida4(bodies: &mut [CelestialBody], dt: f64) {
const W0: f64 = -1.702414383919315;
const W1: f64 = 1.351207191959657;
let c1 = W1 / 2.0;
let c2 = (W0 + W1) / 2.0;
let c3 = c2;
let c4 = c1;
let d1 = W1;
let d2 = W0;
let d3 = W1;
for b in bodies.iter_mut() {
b.position += b.velocity * (c1 * dt);
}
compute_accelerations(bodies);
for b in bodies.iter_mut() {
b.velocity += b.acceleration * (d1 * dt);
}
for b in bodies.iter_mut() {
b.position += b.velocity * (c2 * dt);
}
compute_accelerations(bodies);
for b in bodies.iter_mut() {
b.velocity += b.acceleration * (d2 * dt);
}
for b in bodies.iter_mut() {
b.position += b.velocity * (c3 * dt);
}
compute_accelerations(bodies);
for b in bodies.iter_mut() {
b.velocity += b.acceleration * (d3 * dt);
}
for b in bodies.iter_mut() {
b.position += b.velocity * (c4 * dt);
}
}
pub fn adaptive_step(
bodies: &mut [CelestialBody],
dt: f64,
tolerance: f64,
integrator: &Integrator,
) -> f64 {
let snapshot: Vec<CelestialBody> = bodies.to_vec();
integrator.step(bodies, dt);
let full: Vec<_> = bodies.iter().map(|b| b.position).collect();
bodies.clone_from_slice(&snapshot);
integrator.step(bodies, dt / 2.0);
integrator.step(bodies, dt / 2.0);
let max_err = bodies
.iter()
.zip(full.iter())
.map(|(b, f)| b.position.distance(f) / b.position.magnitude().max(1.0))
.fold(0.0_f64, f64::max);
if max_err > tolerance && dt > 1.0 {
bodies.clone_from_slice(&snapshot);
adaptive_step(bodies, dt / 2.0, tolerance, integrator)
} else if max_err < tolerance * 0.01 {
dt * 2.0
} else {
dt
}
}