use super::DynamicalSystem;
pub struct ThreeBody {
state: Vec<f64>,
pub masses: [f64; 3],
g: f64,
speed: f64,
initial_energy: f64,
pub energy_error: f64,
}
impl ThreeBody {
pub fn new(masses: [f64; 3]) -> Self {
let state = vec![
-0.970_004_36,
0.243_087_53,
0.970_004_36,
-0.243_087_53,
0.0,
0.0,
0.932_407_37 / 2.0,
0.864_731_46 / 2.0,
0.932_407_37 / 2.0,
0.864_731_46 / 2.0,
-0.932_407_37,
-0.864_731_46,
];
let g = 1.0;
let initial_energy = Self::compute_hamiltonian(&state, &masses, g);
Self {
state,
masses,
g,
speed: 0.0,
initial_energy,
energy_error: 0.0,
}
}
fn compute_hamiltonian(state: &[f64], masses: &[f64; 3], g: f64) -> f64 {
let mut t = 0.0f64;
for i in 0..3 {
let vx = state[6 + 2 * i];
let vy = state[6 + 2 * i + 1];
t += 0.5 * (vx * vx + vy * vy) / masses[i];
}
let mut v = 0.0f64;
for i in 0..3 {
for j in (i + 1)..3 {
let dx = state[2 * j] - state[2 * i];
let dy = state[2 * j + 1] - state[2 * i + 1];
let r = (dx * dx + dy * dy).sqrt().max(1e-10);
v -= g * masses[i] * masses[j] / r;
}
}
t + v
}
pub fn hamiltonian(&self) -> f64 {
Self::compute_hamiltonian(&self.state, &self.masses, self.g)
}
fn accelerations(state: &[f64], masses: &[f64; 3], g: f64) -> Vec<f64> {
let pos = |i: usize| (state[2 * i], state[2 * i + 1]);
let mut ax = [0.0f64; 3];
let mut ay = [0.0f64; 3];
for i in 0..3 {
for j in 0..3 {
if i == j {
continue;
}
let (xi, yi) = pos(i);
let (xj, yj) = pos(j);
let dx = xj - xi;
let dy = yj - yi;
let r = (dx * dx + dy * dy).sqrt().max(1e-3);
let r3 = r * r * r;
ax[i] += g * masses[j] * dx / r3;
ay[i] += g * masses[j] * dy / r3;
}
}
vec![ax[0], ay[0], ax[1], ay[1], ax[2], ay[2]]
}
}
impl DynamicalSystem for ThreeBody {
fn state(&self) -> &[f64] {
&self.state
}
fn dimension(&self) -> usize {
12
}
fn name(&self) -> &str {
"Three-Body"
}
fn speed(&self) -> f64 {
self.speed
}
fn deriv_at(&self, state: &[f64]) -> Vec<f64> {
let accel = Self::accelerations(state, &self.masses, self.g);
let mut d = Vec::with_capacity(12);
d.extend_from_slice(&state[6..12]);
d.extend(accel);
d
}
fn energy_error(&self) -> Option<f64> {
Some(self.energy_error)
}
fn set_state(&mut self, s: &[f64]) {
let n = self.state.len().min(s.len());
for i in 0..n {
if s[i].is_finite() {
self.state[i] = s[i];
}
}
self.initial_energy = Self::compute_hamiltonian(&self.state, &self.masses, self.g);
self.energy_error = 0.0;
}
fn step(&mut self, dt: f64) {
let prev = self.state.clone();
let (masses, g) = (self.masses, self.g);
let accel = Self::accelerations(&self.state, &masses, g);
for i in 0..6 {
self.state[6 + i] += 0.5 * dt * accel[i];
}
for i in 0..6 {
self.state[i] += dt * self.state[6 + i];
}
let accel2 = Self::accelerations(&self.state, &masses, g);
for i in 0..6 {
self.state[6 + i] += 0.5 * dt * accel2[i];
}
let ds: f64 = self
.state
.iter()
.zip(prev.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
self.speed = ds / dt;
let h_now = self.hamiltonian();
if self.initial_energy.abs() > 1e-15 {
self.energy_error = ((h_now - self.initial_energy) / self.initial_energy).abs();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::systems::DynamicalSystem;
#[test]
fn test_three_body_initial_state() {
let sys = ThreeBody::new([1.0, 1.0, 1.0]);
let s = sys.state();
assert_eq!(s.len(), 12);
assert_eq!(sys.dimension(), 12);
assert_eq!(sys.name(), "Three-Body");
assert!(s.iter().all(|v| v.is_finite()));
}
#[test]
fn test_three_body_step_changes_state() {
let mut sys = ThreeBody::new([1.0, 1.0, 1.0]);
let before: Vec<f64> = sys.state().to_vec();
sys.step(0.001);
assert!(before.iter().zip(sys.state().iter()).any(|(a, b)| (a - b).abs() > 1e-15));
}
#[test]
fn test_three_body_state_stays_finite() {
let mut sys = ThreeBody::new([1.0, 1.0, 1.0]);
for _ in 0..500 {
sys.step(0.001);
}
for v in sys.state().iter() {
assert!(v.is_finite(), "State became non-finite: {}", v);
}
}
#[test]
fn test_three_body_energy_conserved() {
let mut sys = ThreeBody::new([1.0, 1.0, 1.0]);
for _ in 0..1000 {
sys.step(0.001);
}
assert!(
sys.energy_error < 0.01,
"Energy error too large: {}",
sys.energy_error
);
}
#[test]
fn test_three_body_set_state_resets_energy() {
let mut sys = ThreeBody::new([1.0, 1.0, 1.0]);
for _ in 0..100 {
sys.step(0.01);
}
let new_state: Vec<f64> = (0..12).map(|i| i as f64 * 0.1).collect();
sys.set_state(&new_state);
assert_eq!(sys.energy_error, 0.0, "energy_error should reset after set_state");
}
#[test]
fn test_three_body_deterministic() {
let mut s1 = ThreeBody::new([1.0, 1.0, 1.0]);
let mut s2 = ThreeBody::new([1.0, 1.0, 1.0]);
for _ in 0..200 {
s1.step(0.001);
s2.step(0.001);
}
for (a, b) in s1.state().iter().zip(s2.state().iter()) {
assert!((a - b).abs() < 1e-12, "Non-deterministic: {} vs {}", a, b);
}
}
#[test]
fn test_three_body_speed_positive_after_step() {
let mut sys = ThreeBody::new([1.0, 1.0, 1.0]);
sys.step(0.01);
assert!(sys.speed() > 0.0, "speed should be positive after step: {}", sys.speed());
}
}