Skip to main content

proof_engine/math/
simulation.rs

1//! Simulation systems: cellular automata, N-body physics, reaction-diffusion,
2//! epidemiological models, and traffic simulation.
3
4use std::f64::consts::PI;
5
6// ============================================================
7// SHARED VECTOR TYPE
8// ============================================================
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Vec3 {
12    pub x: f64,
13    pub y: f64,
14    pub z: f64,
15}
16
17impl Vec3 {
18    pub fn new(x: f64, y: f64, z: f64) -> Self { Vec3 { x, y, z } }
19    pub fn zero() -> Self { Vec3 { x: 0.0, y: 0.0, z: 0.0 } }
20    pub fn dot(self, other: Self) -> f64 { self.x * other.x + self.y * other.y + self.z * other.z }
21    pub fn cross(self, other: Self) -> Self {
22        Vec3 {
23            x: self.y * other.z - self.z * other.y,
24            y: self.z * other.x - self.x * other.z,
25            z: self.x * other.y - self.y * other.x,
26        }
27    }
28    pub fn len_sq(self) -> f64 { self.dot(self) }
29    pub fn len(self) -> f64 { self.len_sq().sqrt() }
30    pub fn normalize(self) -> Self {
31        let l = self.len();
32        if l < 1e-300 { return Self::zero(); }
33        Vec3 { x: self.x / l, y: self.y / l, z: self.z / l }
34    }
35    pub fn dist(self, other: Self) -> f64 { (self - other).len() }
36}
37
38impl std::ops::Add for Vec3 {
39    type Output = Self;
40    fn add(self, o: Self) -> Self { Vec3 { x: self.x + o.x, y: self.y + o.y, z: self.z + o.z } }
41}
42impl std::ops::Sub for Vec3 {
43    type Output = Self;
44    fn sub(self, o: Self) -> Self { Vec3 { x: self.x - o.x, y: self.y - o.y, z: self.z - o.z } }
45}
46impl std::ops::Mul<f64> for Vec3 {
47    type Output = Self;
48    fn mul(self, s: f64) -> Self { Vec3 { x: self.x * s, y: self.y * s, z: self.z * s } }
49}
50impl std::ops::Div<f64> for Vec3 {
51    type Output = Self;
52    fn div(self, s: f64) -> Self { Vec3 { x: self.x / s, y: self.y / s, z: self.z / s } }
53}
54impl std::ops::Neg for Vec3 {
55    type Output = Self;
56    fn neg(self) -> Self { Vec3 { x: -self.x, y: -self.y, z: -self.z } }
57}
58impl std::ops::AddAssign for Vec3 {
59    fn add_assign(&mut self, o: Self) { self.x += o.x; self.y += o.y; self.z += o.z; }
60}
61impl std::ops::MulAssign<f64> for Vec3 {
62    fn mul_assign(&mut self, s: f64) { self.x *= s; self.y *= s; self.z *= s; }
63}
64
65// ============================================================
66// CELLULAR AUTOMATA
67// ============================================================
68
69/// Generic 2D cellular automaton grid.
70#[derive(Clone, Debug)]
71pub struct CellularAutomaton<T: Clone> {
72    pub grid: Vec<Vec<T>>,
73    pub width: usize,
74    pub height: usize,
75}
76
77impl<T: Clone + Default> CellularAutomaton<T> {
78    pub fn new(width: usize, height: usize) -> Self {
79        Self {
80            grid: vec![vec![T::default(); width]; height],
81            width,
82            height,
83        }
84    }
85    pub fn get(&self, x: usize, y: usize) -> &T { &self.grid[y][x] }
86    pub fn set(&mut self, x: usize, y: usize, v: T) { self.grid[y][x] = v; }
87    pub fn get_wrapped(&self, x: i64, y: i64) -> &T {
88        let xi = ((x % self.width as i64 + self.width as i64) % self.width as i64) as usize;
89        let yi = ((y % self.height as i64 + self.height as i64) % self.height as i64) as usize;
90        &self.grid[yi][xi]
91    }
92}
93
94// ============================================================
95// GAME OF LIFE
96// ============================================================
97
98/// Conway's Game of Life.
99#[derive(Clone, Debug)]
100pub struct GameOfLife {
101    pub grid: Vec<Vec<bool>>,
102    pub width: usize,
103    pub height: usize,
104}
105
106impl GameOfLife {
107    pub fn new(width: usize, height: usize) -> Self {
108        Self { grid: vec![vec![false; width]; height], width, height }
109    }
110
111    pub fn from_pattern(width: usize, height: usize, pattern: &[(usize, usize)]) -> Self {
112        let mut g = Self::new(width, height);
113        for &(x, y) in pattern {
114            if x < width && y < height { g.grid[y][x] = true; }
115        }
116        g
117    }
118
119    pub fn count_neighbors(&self, x: usize, y: usize) -> u8 {
120        let mut count = 0u8;
121        for dy in -1i64..=1 {
122            for dx in -1i64..=1 {
123                if dx == 0 && dy == 0 { continue; }
124                let nx = ((x as i64 + dx).rem_euclid(self.width as i64)) as usize;
125                let ny = ((y as i64 + dy).rem_euclid(self.height as i64)) as usize;
126                if self.grid[ny][nx] { count += 1; }
127            }
128        }
129        count
130    }
131
132    pub fn step(&mut self) {
133        let old = self.grid.clone();
134        for y in 0..self.height {
135            for x in 0..self.width {
136                let n = {
137                    let mut count = 0u8;
138                    for dy in -1i64..=1 {
139                        for dx in -1i64..=1 {
140                            if dx == 0 && dy == 0 { continue; }
141                            let nx = ((x as i64 + dx).rem_euclid(self.width as i64)) as usize;
142                            let ny = ((y as i64 + dy).rem_euclid(self.height as i64)) as usize;
143                            if old[ny][nx] { count += 1; }
144                        }
145                    }
146                    count
147                };
148                self.grid[y][x] = if old[y][x] {
149                    n == 2 || n == 3
150                } else {
151                    n == 3
152                };
153            }
154        }
155    }
156
157    pub fn count_living(&self) -> usize {
158        self.grid.iter().flat_map(|row| row.iter()).filter(|&&c| c).count()
159    }
160
161    pub fn is_stable(&self) -> bool {
162        let prev = self.grid.clone();
163        let mut tmp = self.clone();
164        tmp.step();
165        tmp.grid == prev
166    }
167
168    /// Standard glider pattern (5x5 bounding box, placed at offset).
169    pub fn place_glider(&mut self, ox: usize, oy: usize) {
170        let cells = [(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)];
171        for (dx, dy) in cells {
172            let x = ox + dx; let y = oy + dy;
173            if x < self.width && y < self.height { self.grid[y][x] = true; }
174        }
175    }
176
177    /// Gosper Glider Gun pattern.
178    pub fn place_glider_gun(&mut self, ox: usize, oy: usize) {
179        let cells: &[(usize, usize)] = &[
180            (0,4),(0,5),(1,4),(1,5),
181            (10,4),(10,5),(10,6),(11,3),(11,7),(12,2),(12,8),(13,2),(13,8),
182            (14,5),(15,3),(15,7),(16,4),(16,5),(16,6),(17,5),
183            (20,2),(20,3),(20,4),(21,2),(21,3),(21,4),(22,1),(22,5),
184            (24,0),(24,1),(24,5),(24,6),
185            (34,2),(34,3),(35,2),(35,3),
186        ];
187        for &(dx, dy) in cells {
188            let x = ox + dx; let y = oy + dy;
189            if x < self.width && y < self.height { self.grid[y][x] = true; }
190        }
191    }
192
193    /// Pulsar pattern (13×13 oscillator, period 3).
194    pub fn place_pulsar(&mut self, ox: usize, oy: usize) {
195        let cols = [2usize, 3, 4, 8, 9, 10];
196        let rows = [0usize, 5, 7, 12];
197        for &r in &rows {
198            for &c in &cols {
199                let x = ox + c; let y = oy + r;
200                if x < self.width && y < self.height { self.grid[y][x] = true; }
201            }
202        }
203        // Transposed
204        for &c in &rows {
205            for &r in &cols {
206                let x = ox + c; let y = oy + r;
207                if x < self.width && y < self.height { self.grid[y][x] = true; }
208            }
209        }
210    }
211
212    /// Lightweight spaceship (LWSS).
213    pub fn place_lwss(&mut self, ox: usize, oy: usize) {
214        let cells = [(1,0),(4,0),(0,1),(0,2),(4,2),(0,3),(1,3),(2,3),(3,3)];
215        for (dx, dy) in cells {
216            let x = ox + dx; let y = oy + dy;
217            if x < self.width && y < self.height { self.grid[y][x] = true; }
218        }
219    }
220
221    /// R-pentomino.
222    pub fn place_r_pentomino(&mut self, ox: usize, oy: usize) {
223        for (dx, dy) in [(1,0),(2,0),(0,1),(1,1),(1,2)] {
224            let x = ox + dx; let y = oy + dy;
225            if x < self.width && y < self.height { self.grid[y][x] = true; }
226        }
227    }
228
229    /// Acorn — small pattern with 5206 gen lifetime.
230    pub fn place_acorn(&mut self, ox: usize, oy: usize) {
231        for (dx, dy) in [(0,1),(1,3),(2,0),(2,1),(4,1),(5,1),(6,1)] {
232            let x = ox + dx; let y = oy + dy;
233            if x < self.width && y < self.height { self.grid[y][x] = true; }
234        }
235    }
236}
237
238// ============================================================
239// WIREWORLD
240// ============================================================
241
242/// WireWorld cell states.
243#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
244pub enum WireWorldCell {
245    #[default]
246    Empty,
247    ElectronHead,
248    ElectronTail,
249    Conductor,
250}
251
252/// WireWorld cellular automaton — simulates digital electronic circuits.
253#[derive(Clone, Debug)]
254pub struct WireWorld {
255    pub grid: Vec<Vec<WireWorldCell>>,
256    pub width: usize,
257    pub height: usize,
258}
259
260impl WireWorld {
261    pub fn new(width: usize, height: usize) -> Self {
262        Self { grid: vec![vec![WireWorldCell::Empty; width]; height], width, height }
263    }
264
265    pub fn set(&mut self, x: usize, y: usize, cell: WireWorldCell) {
266        self.grid[y][x] = cell;
267    }
268
269    pub fn step(&mut self) {
270        let old = self.grid.clone();
271        for y in 0..self.height {
272            for x in 0..self.width {
273                self.grid[y][x] = match old[y][x] {
274                    WireWorldCell::ElectronHead => WireWorldCell::ElectronTail,
275                    WireWorldCell::ElectronTail => WireWorldCell::Conductor,
276                    WireWorldCell::Conductor => {
277                        let heads = self.count_heads_neighbor(&old, x, y);
278                        if heads == 1 || heads == 2 {
279                            WireWorldCell::ElectronHead
280                        } else {
281                            WireWorldCell::Conductor
282                        }
283                    }
284                    WireWorldCell::Empty => WireWorldCell::Empty,
285                };
286            }
287        }
288    }
289
290    fn count_heads_neighbor(&self, grid: &[Vec<WireWorldCell>], x: usize, y: usize) -> usize {
291        let mut count = 0;
292        for dy in -1i64..=1 {
293            for dx in -1i64..=1 {
294                if dx == 0 && dy == 0 { continue; }
295                let nx = x as i64 + dx;
296                let ny = y as i64 + dy;
297                if nx >= 0 && nx < self.width as i64 && ny >= 0 && ny < self.height as i64 {
298                    if grid[ny as usize][nx as usize] == WireWorldCell::ElectronHead { count += 1; }
299                }
300            }
301        }
302        count
303    }
304}
305
306// ============================================================
307// FOREST FIRE
308// ============================================================
309
310/// Cell states for forest fire model.
311#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
312pub enum ForestCell { #[default] Empty, Tree, Burning }
313
314/// Probabilistic forest fire cellular automaton.
315#[derive(Clone, Debug)]
316pub struct ForestFire {
317    pub grid: Vec<Vec<ForestCell>>,
318    pub width: usize,
319    pub height: usize,
320    pub p_grow: f64,    // probability empty→tree
321    pub p_catch: f64,   // probability tree→fire (lightning)
322    rng_state: u64,
323}
324
325impl ForestFire {
326    pub fn new(width: usize, height: usize, p_grow: f64, p_catch: f64, seed: u64) -> Self {
327        Self {
328            grid: vec![vec![ForestCell::Empty; width]; height],
329            width, height, p_grow, p_catch,
330            rng_state: seed,
331        }
332    }
333
334    fn rand(&mut self) -> f64 {
335        self.rng_state = self.rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
336        (self.rng_state >> 33) as f64 / (u32::MAX as f64)
337    }
338
339    pub fn step(&mut self) {
340        let old = self.grid.clone();
341        for y in 0..self.height {
342            for x in 0..self.width {
343                self.grid[y][x] = match old[y][x] {
344                    ForestCell::Burning => ForestCell::Empty,
345                    ForestCell::Empty => {
346                        if self.rand() < self.p_grow { ForestCell::Tree } else { ForestCell::Empty }
347                    }
348                    ForestCell::Tree => {
349                        let neighbor_burning = (-1i64..=1).any(|dy| {
350                            (-1i64..=1).any(|dx| {
351                                if dx == 0 && dy == 0 { return false; }
352                                let nx = x as i64 + dx; let ny = y as i64 + dy;
353                                nx >= 0 && nx < self.width as i64 && ny >= 0 && ny < self.height as i64
354                                    && old[ny as usize][nx as usize] == ForestCell::Burning
355                            })
356                        });
357                        if neighbor_burning || self.rand() < self.p_catch {
358                            ForestCell::Burning
359                        } else {
360                            ForestCell::Tree
361                        }
362                    }
363                };
364            }
365        }
366    }
367
368    pub fn count_trees(&self) -> usize {
369        self.grid.iter().flat_map(|r| r.iter()).filter(|&&c| c == ForestCell::Tree).count()
370    }
371
372    pub fn count_burning(&self) -> usize {
373        self.grid.iter().flat_map(|r| r.iter()).filter(|&&c| c == ForestCell::Burning).count()
374    }
375}
376
377// ============================================================
378// BRIAN'S BRAIN
379// ============================================================
380
381/// Brian's Brain 3-state automaton.
382#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
383pub enum BrianCell { #[default] Off, On, Dying }
384
385#[derive(Clone, Debug)]
386pub struct BriansBrain {
387    pub grid: Vec<Vec<BrianCell>>,
388    pub width: usize,
389    pub height: usize,
390}
391
392impl BriansBrain {
393    pub fn new(width: usize, height: usize) -> Self {
394        Self { grid: vec![vec![BrianCell::Off; width]; height], width, height }
395    }
396
397    pub fn step(&mut self) {
398        let old = self.grid.clone();
399        for y in 0..self.height {
400            for x in 0..self.width {
401                self.grid[y][x] = match old[y][x] {
402                    BrianCell::On => BrianCell::Dying,
403                    BrianCell::Dying => BrianCell::Off,
404                    BrianCell::Off => {
405                        let on_count = (-1i64..=1).flat_map(|dy| {
406                            (-1i64..=1).map(move |dx| (dx, dy))
407                        }).filter(|&(dx, dy)| {
408                            if dx == 0 && dy == 0 { return false; }
409                            let nx = x as i64 + dx; let ny = y as i64 + dy;
410                            nx >= 0 && nx < self.width as i64 && ny >= 0 && ny < self.height as i64
411                                && old[ny as usize][nx as usize] == BrianCell::On
412                        }).count();
413                        if on_count == 2 { BrianCell::On } else { BrianCell::Off }
414                    }
415                };
416            }
417        }
418    }
419
420    pub fn count_on(&self) -> usize {
421        self.grid.iter().flat_map(|r| r.iter()).filter(|&&c| c == BrianCell::On).count()
422    }
423}
424
425// ============================================================
426// LANGTON'S ANT
427// ============================================================
428
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum AntDir { North, East, South, West }
431
432impl AntDir {
433    fn turn_right(self) -> Self {
434        match self { Self::North => Self::East, Self::East => Self::South, Self::South => Self::West, Self::West => Self::North }
435    }
436    fn turn_left(self) -> Self {
437        match self { Self::North => Self::West, Self::West => Self::South, Self::South => Self::East, Self::East => Self::North }
438    }
439    fn delta(self) -> (i64, i64) {
440        match self { Self::North => (0,-1), Self::East => (1,0), Self::South => (0,1), Self::West => (-1,0) }
441    }
442}
443
444/// Langton's Ant — universal Turing machine on a 2D grid.
445#[derive(Clone, Debug)]
446pub struct LangtonsAnt {
447    pub grid: std::collections::HashMap<(i64, i64), bool>,
448    pub ant_pos: (i64, i64),
449    pub ant_dir: AntDir,
450    pub steps: u64,
451}
452
453impl LangtonsAnt {
454    pub fn new() -> Self {
455        Self {
456            grid: std::collections::HashMap::new(),
457            ant_pos: (0, 0),
458            ant_dir: AntDir::North,
459            steps: 0,
460        }
461    }
462
463    pub fn step(&mut self) {
464        let cell = self.grid.get(&self.ant_pos).copied().unwrap_or(false);
465        if !cell {
466            // White cell: turn right, flip to black, move forward
467            self.ant_dir = self.ant_dir.turn_right();
468            self.grid.insert(self.ant_pos, true);
469        } else {
470            // Black cell: turn left, flip to white, move forward
471            self.ant_dir = self.ant_dir.turn_left();
472            self.grid.insert(self.ant_pos, false);
473        }
474        let (dx, dy) = self.ant_dir.delta();
475        self.ant_pos = (self.ant_pos.0 + dx, self.ant_pos.1 + dy);
476        self.steps += 1;
477    }
478
479    pub fn step_n(&mut self, n: usize) {
480        for _ in 0..n { self.step(); }
481    }
482
483    pub fn count_black(&self) -> usize {
484        self.grid.values().filter(|&&v| v).count()
485    }
486}
487
488impl Default for LangtonsAnt {
489    fn default() -> Self { Self::new() }
490}
491
492// ============================================================
493// WOLFRAM ELEMENTARY CA (1D)
494// ============================================================
495
496/// Wolfram elementary 1D cellular automaton (256 rules).
497#[derive(Clone, Debug)]
498pub struct Totalistic1D {
499    pub rule: u8,
500}
501
502impl Totalistic1D {
503    pub fn new(rule: u8) -> Self { Self { rule } }
504
505    /// Apply rule to (left, center, right) booleans.
506    #[inline]
507    fn apply(&self, l: bool, c: bool, r: bool) -> bool {
508        let idx = ((l as u8) << 2) | ((c as u8) << 1) | (r as u8);
509        (self.rule >> idx) & 1 == 1
510    }
511
512    /// Evolve a row for `steps` generations. Returns all generations including initial.
513    pub fn evolve(&self, initial: Vec<bool>, steps: usize) -> Vec<Vec<bool>> {
514        let n = initial.len();
515        let mut result = Vec::with_capacity(steps + 1);
516        result.push(initial);
517        for _ in 0..steps {
518            let prev = result.last().unwrap();
519            let mut next = vec![false; n];
520            for i in 0..n {
521                let l = if i == 0 { prev[n - 1] } else { prev[i - 1] };
522                let c = prev[i];
523                let r = if i == n - 1 { prev[0] } else { prev[i + 1] };
524                next[i] = self.apply(l, c, r);
525            }
526            result.push(next);
527        }
528        result
529    }
530
531    /// Rule 30 used as a pseudo-random number generator.
532    pub fn rule_30_random(x: u64) -> bool {
533        // Run rule 30 for 64 generations using bit at position 32 as seed
534        let ca = Totalistic1D::new(30);
535        let n = 128usize;
536        let mut row = vec![false; n];
537        row[n / 2] = true;
538        let steps = (x % 64) as usize + 10;
539        let result = ca.evolve(row, steps);
540        let last = result.last().unwrap();
541        last[n / 2]
542    }
543}
544
545// ============================================================
546// N-BODY SIMULATION
547// ============================================================
548
549/// A gravitational body.
550#[derive(Clone, Debug)]
551pub struct Body {
552    pub position: Vec3,
553    pub velocity: Vec3,
554    pub mass: f64,
555    pub radius: f64,
556}
557
558/// N-body simulation state.
559#[derive(Clone, Debug)]
560pub struct NBodySimulation {
561    pub bodies: Vec<Body>,
562    pub dt: f64,
563    pub time: f64,
564    pub softening: f64, // gravitational softening parameter
565}
566
567impl NBodySimulation {
568    pub fn new(bodies: Vec<Body>, dt: f64) -> Self {
569        Self { bodies, dt, time: 0.0, softening: 1e-4 }
570    }
571}
572
573/// Direct O(n²) gravity calculation and Euler step.
574pub fn direct_gravity_step(sim: &mut NBodySimulation) {
575    let n = sim.bodies.len();
576    let eps2 = sim.softening * sim.softening;
577    let mut accels = vec![Vec3::zero(); n];
578    const G: f64 = 6.674e-11;
579    for i in 0..n {
580        for j in 0..n {
581            if i == j { continue; }
582            let r = sim.bodies[j].position - sim.bodies[i].position;
583            let r2 = r.len_sq() + eps2;
584            let r3 = r2 * r2.sqrt();
585            let a = G * sim.bodies[j].mass / r3;
586            accels[i] += r * a;
587        }
588    }
589    for i in 0..n {
590        sim.bodies[i].velocity += accels[i] * sim.dt;
591        let vel = sim.bodies[i].velocity;
592        sim.bodies[i].position += vel * sim.dt;
593    }
594    sim.time += sim.dt;
595}
596
597/// Leapfrog (Störmer-Verlet) symplectic N-body step.
598pub fn leapfrog_step(sim: &mut NBodySimulation) {
599    let n = sim.bodies.len();
600    let eps2 = sim.softening * sim.softening;
601    const G: f64 = 6.674e-11;
602    let compute_accels = |bodies: &[Body]| -> Vec<Vec3> {
603        let mut accels = vec![Vec3::zero(); bodies.len()];
604        for i in 0..bodies.len() {
605            for j in 0..bodies.len() {
606                if i == j { continue; }
607                let r = bodies[j].position - bodies[i].position;
608                let r2 = r.len_sq() + eps2;
609                let r3 = r2 * r2.sqrt();
610                accels[i] += r * (G * bodies[j].mass / r3);
611            }
612        }
613        accels
614    };
615    // Half-kick velocities
616    let acc0 = compute_accels(&sim.bodies);
617    for i in 0..n { sim.bodies[i].velocity += acc0[i] * (sim.dt * 0.5); }
618    // Full drift positions
619    for i in 0..n {
620        let v = sim.bodies[i].velocity;
621        sim.bodies[i].position += v * sim.dt;
622    }
623    // Second half-kick
624    let acc1 = compute_accels(&sim.bodies);
625    for i in 0..n { sim.bodies[i].velocity += acc1[i] * (sim.dt * 0.5); }
626    sim.time += sim.dt;
627}
628
629// ============================================================
630// BARNES-HUT OCTREE
631// ============================================================
632
633/// Node in the Barnes-Hut octree.
634#[derive(Clone, Debug)]
635pub enum OctreeNode {
636    Empty,
637    Leaf { body_idx: usize, pos: Vec3, mass: f64 },
638    Internal {
639        center_of_mass: Vec3,
640        total_mass: f64,
641        children: Box<[OctreeNode; 8]>,
642        min: Vec3,
643        max: Vec3,
644    },
645}
646
647impl OctreeNode {
648    fn child_idx(center: Vec3, pos: Vec3) -> usize {
649        let x = if pos.x >= center.x { 1 } else { 0 };
650        let y = if pos.y >= center.y { 2 } else { 0 };
651        let z = if pos.z >= center.z { 4 } else { 0 };
652        x | y | z
653    }
654
655    fn child_bounds(min: Vec3, max: Vec3, idx: usize) -> (Vec3, Vec3) {
656        let center = (min + max) * 0.5;
657        let child_min = Vec3::new(
658            if idx & 1 != 0 { center.x } else { min.x },
659            if idx & 2 != 0 { center.y } else { min.y },
660            if idx & 4 != 0 { center.z } else { min.z },
661        );
662        let child_max = Vec3::new(
663            if idx & 1 != 0 { max.x } else { center.x },
664            if idx & 2 != 0 { max.y } else { center.y },
665            if idx & 4 != 0 { max.z } else { center.z },
666        );
667        (child_min, child_max)
668    }
669
670    pub fn insert(&mut self, body_idx: usize, pos: Vec3, mass: f64, min: Vec3, max: Vec3) {
671        match self {
672            OctreeNode::Empty => {
673                *self = OctreeNode::Leaf { body_idx, pos, mass };
674            }
675            OctreeNode::Leaf { body_idx: bi, pos: bp, mass: bm } => {
676                let old_bi = *bi; let old_pos = *bp; let old_mass = *bm;
677                let center = (min + max) * 0.5;
678                let children: [OctreeNode; 8] = Default::default();
679                *self = OctreeNode::Internal {
680                    center_of_mass: (old_pos * old_mass + pos * mass) * (1.0 / (old_mass + mass)),
681                    total_mass: old_mass + mass,
682                    children: Box::new(children),
683                    min, max,
684                };
685                if let OctreeNode::Internal { children, min, max, .. } = self {
686                    let ci = Self::child_idx(((*min + *max) * 0.5), old_pos);
687                    let (cmin, cmax) = Self::child_bounds(*min, *max, ci);
688                    children[ci].insert(old_bi, old_pos, old_mass, cmin, cmax);
689                    let ci2 = Self::child_idx(((*min + *max) * 0.5), pos);
690                    let (cmin2, cmax2) = Self::child_bounds(*min, *max, ci2);
691                    children[ci2].insert(body_idx, pos, mass, cmin2, cmax2);
692                }
693            }
694            OctreeNode::Internal { center_of_mass, total_mass, children, min, max } => {
695                *total_mass += mass;
696                *center_of_mass = (*center_of_mass * (*total_mass - mass) + pos * mass) * (1.0 / *total_mass);
697                let ci = Self::child_idx((*min + *max) * 0.5, pos);
698                let (cmin, cmax) = Self::child_bounds(*min, *max, ci);
699                children[ci].insert(body_idx, pos, mass, cmin, cmax);
700            }
701        }
702    }
703
704    pub fn compute_accel(&self, pos: Vec3, theta: f64, eps2: f64) -> Vec3 {
705        const G: f64 = 6.674e-11;
706        match self {
707            OctreeNode::Empty => Vec3::zero(),
708            OctreeNode::Leaf { pos: lp, mass, .. } => {
709                let r = *lp - pos;
710                let r2 = r.len_sq() + eps2;
711                if r2 < eps2 * 100.0 { return Vec3::zero(); }
712                r * (G * mass / (r2 * r2.sqrt()))
713            }
714            OctreeNode::Internal { center_of_mass, total_mass, children, min, max } => {
715                let size = (max.x - min.x).max((max.y - min.y).max(max.z - min.z));
716                let r = *center_of_mass - pos;
717                let dist = r.len();
718                if size / dist < theta {
719                    let r2 = r.len_sq() + eps2;
720                    r * (G * total_mass / (r2 * r2.sqrt()))
721                } else {
722                    children.iter().map(|c| c.compute_accel(pos, theta, eps2)).fold(Vec3::zero(), |a, b| a + b)
723                }
724            }
725        }
726    }
727}
728
729impl Default for OctreeNode {
730    fn default() -> Self { OctreeNode::Empty }
731}
732
733/// Barnes-Hut O(n log n) gravity step.
734pub fn barnes_hut_step(sim: &mut NBodySimulation, theta: f64) {
735    if sim.bodies.is_empty() { return; }
736    // Find bounding box
737    let mut min = sim.bodies[0].position;
738    let mut max = sim.bodies[0].position;
739    for b in &sim.bodies {
740        min.x = min.x.min(b.position.x); max.x = max.x.max(b.position.x);
741        min.y = min.y.min(b.position.y); max.y = max.y.max(b.position.y);
742        min.z = min.z.min(b.position.z); max.z = max.z.max(b.position.z);
743    }
744    let padding = 1.0;
745    min = min - Vec3::new(padding, padding, padding);
746    max = max + Vec3::new(padding, padding, padding);
747
748    // Build octree
749    let mut root = OctreeNode::Empty;
750    for (i, b) in sim.bodies.iter().enumerate() {
751        root.insert(i, b.position, b.mass, min, max);
752    }
753
754    // Compute accelerations
755    let eps2 = sim.softening * sim.softening;
756    let n = sim.bodies.len();
757    let accels: Vec<Vec3> = (0..n)
758        .map(|i| root.compute_accel(sim.bodies[i].position, theta, eps2))
759        .collect();
760
761    for i in 0..n {
762        sim.bodies[i].velocity += accels[i] * sim.dt;
763        let vel = sim.bodies[i].velocity;
764        sim.bodies[i].position += vel * sim.dt;
765    }
766    sim.time += sim.dt;
767}
768
769// ============================================================
770// ORBITAL MECHANICS
771// ============================================================
772
773/// Classical orbital elements.
774#[derive(Clone, Debug)]
775pub struct OrbitalElements {
776    pub semi_major_axis: f64,    // a
777    pub eccentricity: f64,       // e
778    pub inclination: f64,        // i (radians)
779    pub lan: f64,                // Longitude of ascending node (radians)
780    pub arg_periapsis: f64,      // ω (radians)
781    pub true_anomaly: f64,       // ν (radians)
782}
783
784/// Compute orbital elements from state vectors.
785pub fn from_state_vectors(r: Vec3, v: Vec3, mu: f64) -> OrbitalElements {
786    let h = r.cross(v); // specific angular momentum
787    let r_len = r.len();
788    let v_len = v.len();
789
790    // Eccentricity vector
791    let e_vec = r * (v_len * v_len / mu - 1.0 / r_len) - v * (r.dot(v) / mu);
792    let ecc = e_vec.len();
793
794    let energy = v_len * v_len / 2.0 - mu / r_len;
795    let a = if energy.abs() > 1e-12 { -mu / (2.0 * energy) } else { 1e30 };
796
797    let i = (h.z / h.len()).acos().min(PI).max(0.0);
798    let n = Vec3::new(-h.y, h.x, 0.0); // ascending node vector
799    let n_len = n.len();
800
801    let lan = if n_len > 1e-12 {
802        let mut lan = (n.x / n_len).acos();
803        if n.y < 0.0 { lan = 2.0 * PI - lan; }
804        lan
805    } else { 0.0 };
806
807    let arg_p = if n_len > 1e-12 && ecc > 1e-12 {
808        let mut omega = (n.dot(e_vec) / (n_len * ecc)).clamp(-1.0, 1.0).acos();
809        if e_vec.z < 0.0 { omega = 2.0 * PI - omega; }
810        omega
811    } else { 0.0 };
812
813    let nu = if ecc > 1e-12 {
814        let mut nu = (e_vec.dot(r) / (ecc * r_len)).clamp(-1.0, 1.0).acos();
815        if r.dot(v) < 0.0 { nu = 2.0 * PI - nu; }
816        nu
817    } else { 0.0 };
818
819    OrbitalElements {
820        semi_major_axis: a,
821        eccentricity: ecc,
822        inclination: i,
823        lan,
824        arg_periapsis: arg_p,
825        true_anomaly: nu,
826    }
827}
828
829/// Convert orbital elements to state vectors.
830pub fn to_state_vectors(elems: &OrbitalElements, mu: f64) -> (Vec3, Vec3) {
831    let a = elems.semi_major_axis;
832    let e = elems.eccentricity;
833    let i = elems.inclination;
834    let lan = elems.lan;
835    let omega = elems.arg_periapsis;
836    let nu = elems.true_anomaly;
837
838    let p = a * (1.0 - e * e);
839    let r_perifocal = p / (1.0 + e * nu.cos());
840
841    let r_peri = Vec3::new(r_perifocal * nu.cos(), r_perifocal * nu.sin(), 0.0);
842    let v_peri = Vec3::new(
843        -(mu / p).sqrt() * nu.sin(),
844        (mu / p).sqrt() * (e + nu.cos()),
845        0.0,
846    );
847
848    // Rotation matrices
849    let rx = |theta: f64, v: Vec3| -> Vec3 {
850        Vec3::new(v.x, v.y * theta.cos() - v.z * theta.sin(), v.y * theta.sin() + v.z * theta.cos())
851    };
852    let rz = |theta: f64, v: Vec3| -> Vec3 {
853        Vec3::new(v.x * theta.cos() - v.y * theta.sin(), v.x * theta.sin() + v.y * theta.cos(), v.z)
854    };
855
856    let r_rot = rz(-lan, rx(-i, rz(-omega, r_peri)));
857    let v_rot = rz(-lan, rx(-i, rz(-omega, v_peri)));
858
859    (r_rot, v_rot)
860}
861
862/// Solve Kepler's equation M = E - e*sin(E) for eccentric anomaly E.
863pub fn solve_kepler_equation(m_anom: f64, e: f64) -> f64 {
864    let mut e_anom = if e < 0.8 { m_anom } else { PI };
865    for _ in 0..100 {
866        let f = e_anom - e * e_anom.sin() - m_anom;
867        let df = 1.0 - e * e_anom.cos();
868        if df.abs() < 1e-12 { break; }
869        let delta = f / df;
870        e_anom -= delta;
871        if delta.abs() < 1e-12 { break; }
872    }
873    e_anom
874}
875
876// ============================================================
877// REACTION-DIFFUSION
878// ============================================================
879
880/// Gray-Scott reaction-diffusion system.
881/// Models Turing pattern formation.
882#[derive(Clone, Debug)]
883pub struct GrayScott {
884    pub u: Vec<f64>,
885    pub v: Vec<f64>,
886    pub width: usize,
887    pub height: usize,
888    pub feed: f64,
889    pub kill: f64,
890    pub du: f64,
891    pub dv: f64,
892}
893
894/// Preset parameters for Gray-Scott system.
895#[derive(Clone, Copy, Debug)]
896pub struct GrayScottPreset {
897    pub feed: f64,
898    pub kill: f64,
899    pub name: &'static str,
900}
901
902impl GrayScott {
903    pub fn new(width: usize, height: usize, feed: f64, kill: f64) -> Self {
904        let n = width * height;
905        Self {
906            u: vec![1.0; n],
907            v: vec![0.0; n],
908            width, height, feed, kill,
909            du: 0.2,
910            dv: 0.1,
911        }
912    }
913
914    pub fn seed_center(&mut self) {
915        let cx = self.width / 2;
916        let cy = self.height / 2;
917        let r = 5.min(self.width.min(self.height) / 4);
918        for y in (cy.saturating_sub(r))..(cy + r).min(self.height) {
919            for x in (cx.saturating_sub(r))..(cx + r).min(self.width) {
920                let idx = y * self.width + x;
921                self.u[idx] = 0.5;
922                self.v[idx] = 0.25;
923            }
924        }
925    }
926
927    #[inline]
928    fn idx(&self, x: usize, y: usize) -> usize { y * self.width + x }
929
930    fn laplacian_u(&self, x: usize, y: usize) -> f64 {
931        let cx = x;
932        let cy = y;
933        let xm = if cx == 0 { self.width - 1 } else { cx - 1 };
934        let xp = if cx == self.width - 1 { 0 } else { cx + 1 };
935        let ym = if cy == 0 { self.height - 1 } else { cy - 1 };
936        let yp = if cy == self.height - 1 { 0 } else { cy + 1 };
937        self.u[self.idx(xm, cy)] + self.u[self.idx(xp, cy)]
938            + self.u[self.idx(cx, ym)] + self.u[self.idx(cx, yp)]
939            - 4.0 * self.u[self.idx(cx, cy)]
940    }
941
942    fn laplacian_v(&self, x: usize, y: usize) -> f64 {
943        let cx = x; let cy = y;
944        let xm = if cx == 0 { self.width - 1 } else { cx - 1 };
945        let xp = if cx == self.width - 1 { 0 } else { cx + 1 };
946        let ym = if cy == 0 { self.height - 1 } else { cy - 1 };
947        let yp = if cy == self.height - 1 { 0 } else { cy + 1 };
948        self.v[self.idx(xm, cy)] + self.v[self.idx(xp, cy)]
949            + self.v[self.idx(cx, ym)] + self.v[self.idx(cx, yp)]
950            - 4.0 * self.v[self.idx(cx, cy)]
951    }
952
953    pub fn step(&mut self, dt: f64) {
954        let n = self.width * self.height;
955        let mut u_new = vec![0.0f64; n];
956        let mut v_new = vec![0.0f64; n];
957        for y in 0..self.height {
958            for x in 0..self.width {
959                let i = self.idx(x, y);
960                let u = self.u[i];
961                let v = self.v[i];
962                let uvv = u * v * v;
963                u_new[i] = u + dt * (self.du * self.laplacian_u(x, y) - uvv + self.feed * (1.0 - u));
964                v_new[i] = v + dt * (self.dv * self.laplacian_v(x, y) + uvv - (self.feed + self.kill) * v);
965                u_new[i] = u_new[i].clamp(0.0, 1.0);
966                v_new[i] = v_new[i].clamp(0.0, 1.0);
967            }
968        }
969        self.u = u_new;
970        self.v = v_new;
971    }
972
973    /// Preset: mitosis (f=0.0367, k=0.0649)
974    pub fn preset_mitosis(width: usize, height: usize) -> Self {
975        Self::new(width, height, 0.0367, 0.0649)
976    }
977
978    /// Preset: coral (f=0.0545, k=0.062)
979    pub fn preset_coral(width: usize, height: usize) -> Self {
980        Self::new(width, height, 0.0545, 0.062)
981    }
982
983    /// Preset: worms (f=0.078, k=0.061)
984    pub fn preset_worms(width: usize, height: usize) -> Self {
985        Self::new(width, height, 0.078, 0.061)
986    }
987
988    /// Preset: maze (f=0.029, k=0.057)
989    pub fn preset_maze(width: usize, height: usize) -> Self {
990        Self::new(width, height, 0.029, 0.057)
991    }
992
993    /// Preset: solitons (f=0.03, k=0.06)
994    pub fn preset_solitons(width: usize, height: usize) -> Self {
995        Self::new(width, height, 0.03, 0.06)
996    }
997
998    /// Preset: unstable (f=0.02, k=0.055)
999    pub fn preset_unstable(width: usize, height: usize) -> Self {
1000        Self::new(width, height, 0.02, 0.055)
1001    }
1002}
1003
1004// ============================================================
1005// FITZHUGH-NAGUMO
1006// ============================================================
1007
1008/// FitzHugh-Nagumo excitable medium — models action potentials and spiral waves.
1009#[derive(Clone, Debug)]
1010pub struct FitzHughNagumo {
1011    pub v: Vec<f64>,   // fast variable (voltage)
1012    pub w: Vec<f64>,   // slow recovery variable
1013    pub width: usize,
1014    pub height: usize,
1015    pub a: f64,        // threshold parameter
1016    pub b: f64,        // recovery coupling
1017    pub tau: f64,      // time scale separation
1018    pub eps: f64,      // diffusion
1019}
1020
1021impl FitzHughNagumo {
1022    pub fn new(width: usize, height: usize) -> Self {
1023        let n = width * height;
1024        Self {
1025            v: vec![0.0; n],
1026            w: vec![0.0; n],
1027            width, height,
1028            a: 0.7, b: 0.8, tau: 12.5, eps: 0.1,
1029        }
1030    }
1031
1032    pub fn stimulate(&mut self, x: usize, y: usize, radius: usize) {
1033        for dy in 0..=radius * 2 {
1034            for dx in 0..=radius * 2 {
1035                let nx = (x + dx).saturating_sub(radius);
1036                let ny = (y + dy).saturating_sub(radius);
1037                if nx < self.width && ny < self.height {
1038                    let i = ny * self.width + nx;
1039                    self.v[i] = 1.0;
1040                }
1041            }
1042        }
1043    }
1044
1045    fn laplacian(data: &[f64], x: usize, y: usize, w: usize, h: usize) -> f64 {
1046        let idx = |x: usize, y: usize| y * w + x;
1047        let xm = if x == 0 { w - 1 } else { x - 1 };
1048        let xp = if x == w - 1 { 0 } else { x + 1 };
1049        let ym = if y == 0 { h - 1 } else { y - 1 };
1050        let yp = if y == h - 1 { 0 } else { y + 1 };
1051        data[idx(xm, y)] + data[idx(xp, y)] + data[idx(x, ym)] + data[idx(x, yp)] - 4.0 * data[idx(x, y)]
1052    }
1053
1054    pub fn step(&mut self, dt: f64) {
1055        let n = self.width * self.height;
1056        let mut v_new = vec![0.0f64; n];
1057        let mut w_new = vec![0.0f64; n];
1058        for y in 0..self.height {
1059            for x in 0..self.width {
1060                let i = y * self.width + x;
1061                let v = self.v[i];
1062                let w = self.w[i];
1063                let lap_v = Self::laplacian(&self.v, x, y, self.width, self.height);
1064                let dv = v - v.powi(3) / 3.0 - w + self.eps * lap_v;
1065                let dw = (v + self.a - self.b * w) / self.tau;
1066                v_new[i] = v + dt * dv;
1067                w_new[i] = w + dt * dw;
1068            }
1069        }
1070        self.v = v_new;
1071        self.w = w_new;
1072    }
1073}
1074
1075// ============================================================
1076// BELOUSOV-ZHABOTINSKY
1077// ============================================================
1078
1079/// Parameters for the Belousov-Zhabotinsky oscillator.
1080#[derive(Clone, Debug)]
1081pub struct BzParams {
1082    pub alpha: f64,
1083    pub beta: f64,
1084    pub gamma: f64,
1085}
1086
1087impl Default for BzParams {
1088    fn default() -> Self { BzParams { alpha: 0.1, beta: 0.1, gamma: 0.1 } }
1089}
1090
1091/// Simplified 3-variable BZ reaction model on a 2D grid.
1092#[derive(Clone, Debug)]
1093pub struct BelousovZhabotinsky {
1094    /// Each cell stores 3 concentrations [u, v, w].
1095    pub concentrations: Vec<Vec<f64>>,
1096    pub width: usize,
1097    pub height: usize,
1098    pub params: BzParams,
1099}
1100
1101impl BelousovZhabotinsky {
1102    pub fn new(width: usize, height: usize, params: BzParams) -> Self {
1103        let n = width * height;
1104        // Random-ish initial concentrations
1105        let mut concentrations = vec![vec![0.0f64; 3]; n];
1106        let mut state = 12345u64;
1107        for row in &mut concentrations {
1108            for v in row.iter_mut() {
1109                state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
1110                *v = (state >> 33) as f64 / (u32::MAX as f64);
1111            }
1112        }
1113        Self { concentrations, width, height, params }
1114    }
1115
1116    fn laplacian_conc(data: &[Vec<f64>], comp: usize, x: usize, y: usize, w: usize, h: usize) -> f64 {
1117        let idx = |x: usize, y: usize| y * w + x;
1118        let xm = if x == 0 { w - 1 } else { x - 1 };
1119        let xp = if x == w - 1 { 0 } else { x + 1 };
1120        let ym = if y == 0 { h - 1 } else { y - 1 };
1121        let yp = if y == h - 1 { 0 } else { y + 1 };
1122        data[idx(xm, y)][comp] + data[idx(xp, y)][comp]
1123            + data[idx(x, ym)][comp] + data[idx(x, yp)][comp]
1124            - 4.0 * data[idx(x, y)][comp]
1125    }
1126
1127    pub fn step(&mut self, dt: f64) {
1128        let n = self.width * self.height;
1129        let mut new_c = self.concentrations.clone();
1130        let alpha = self.params.alpha;
1131        let beta = self.params.beta;
1132        let gamma = self.params.gamma;
1133        for y in 0..self.height {
1134            for x in 0..self.width {
1135                let i = y * self.width + x;
1136                let u = self.concentrations[i][0];
1137                let v = self.concentrations[i][1];
1138                let w = self.concentrations[i][2];
1139                let lap_u = Self::laplacian_conc(&self.concentrations, 0, x, y, self.width, self.height);
1140                let lap_v = Self::laplacian_conc(&self.concentrations, 1, x, y, self.width, self.height);
1141                let lap_w = Self::laplacian_conc(&self.concentrations, 2, x, y, self.width, self.height);
1142                // Oregonator-inspired simplified ODE
1143                let du = u * (1.0 - u - alpha * v / (u + beta)) + 0.1 * lap_u;
1144                let dv = u - v + 0.05 * lap_v;
1145                let dw = gamma * (u - w) + 0.02 * lap_w;
1146                new_c[i][0] = (u + dt * du).clamp(0.0, 1.0);
1147                new_c[i][1] = (v + dt * dv).clamp(0.0, 1.0);
1148                new_c[i][2] = (w + dt * dw).clamp(0.0, 1.0);
1149            }
1150        }
1151        self.concentrations = new_c;
1152    }
1153}
1154
1155// ============================================================
1156// EPIDEMIOLOGICAL MODELS
1157// ============================================================
1158
1159/// Susceptible-Infected-Recovered (SIR) model.
1160#[derive(Clone, Debug)]
1161pub struct SirModel {
1162    pub s: f64,     // susceptible fraction
1163    pub i: f64,     // infected fraction
1164    pub r: f64,     // recovered fraction
1165    pub n: f64,     // total population
1166    pub beta: f64,  // transmission rate
1167    pub gamma: f64, // recovery rate
1168    pub time: f64,
1169}
1170
1171impl SirModel {
1172    pub fn new(s0: f64, i0: f64, n: f64, beta: f64, gamma: f64) -> Self {
1173        let r0 = n - s0 - i0;
1174        Self { s: s0, i: i0, r: r0.max(0.0), n, beta, gamma, time: 0.0 }
1175    }
1176
1177    pub fn step(&mut self, dt: f64) {
1178        let ds = -self.beta * self.s * self.i / self.n;
1179        let di = self.beta * self.s * self.i / self.n - self.gamma * self.i;
1180        let dr = self.gamma * self.i;
1181        self.s = (self.s + dt * ds).max(0.0);
1182        self.i = (self.i + dt * di).max(0.0);
1183        self.r = (self.r + dt * dr).max(0.0);
1184        self.time += dt;
1185    }
1186
1187    pub fn basic_reproduction_number(&self) -> f64 {
1188        self.beta / self.gamma
1189    }
1190
1191    pub fn time_to_peak(&self) -> f64 {
1192        // Approximate: t_peak ~ (1/(beta-gamma)) * ln(beta*S0/(gamma*N))
1193        let r0 = self.basic_reproduction_number();
1194        if r0 <= 1.0 { return f64::INFINITY; }
1195        let beta = self.beta; let gamma = self.gamma;
1196        (1.0 / (beta - gamma)) * (beta * self.s / (gamma * self.n)).ln().abs()
1197    }
1198
1199    pub fn herd_immunity_threshold(&self) -> f64 {
1200        1.0 - 1.0 / self.basic_reproduction_number()
1201    }
1202}
1203
1204/// SEIRD model with Exposed and Deceased compartments.
1205#[derive(Clone, Debug)]
1206pub struct SeirdModel {
1207    pub s: f64,      // susceptible
1208    pub e: f64,      // exposed (latent)
1209    pub i: f64,      // infectious
1210    pub r: f64,      // recovered
1211    pub d: f64,      // deceased
1212    pub n: f64,      // total population
1213    pub beta: f64,   // transmission rate
1214    pub sigma: f64,  // rate E→I (1/incubation period)
1215    pub gamma: f64,  // recovery rate
1216    pub delta: f64,  // mortality rate
1217    pub time: f64,
1218}
1219
1220impl SeirdModel {
1221    pub fn new(s0: f64, e0: f64, i0: f64, n: f64, beta: f64, sigma: f64, gamma: f64, delta: f64) -> Self {
1222        Self { s: s0, e: e0, i: i0, r: 0.0, d: 0.0, n, beta, sigma, gamma, delta, time: 0.0 }
1223    }
1224
1225    pub fn step(&mut self, dt: f64) {
1226        let force = self.beta * self.s * self.i / self.n;
1227        let ds = -force;
1228        let de = force - self.sigma * self.e;
1229        let di = self.sigma * self.e - (self.gamma + self.delta) * self.i;
1230        let dr = self.gamma * self.i;
1231        let dd = self.delta * self.i;
1232        self.s = (self.s + dt * ds).max(0.0);
1233        self.e = (self.e + dt * de).max(0.0);
1234        self.i = (self.i + dt * di).max(0.0);
1235        self.r = (self.r + dt * dr).max(0.0);
1236        self.d = (self.d + dt * dd).max(0.0);
1237        self.time += dt;
1238    }
1239
1240    pub fn total_active(&self) -> f64 { self.s + self.e + self.i }
1241}
1242
1243/// Per-cell state for spatial SIR model.
1244#[derive(Clone, Copy, Debug, Default)]
1245pub struct SirCell {
1246    pub s: f64,
1247    pub i: f64,
1248    pub r: f64,
1249}
1250
1251/// Spatial SIR model on a 2D grid.
1252#[derive(Clone, Debug)]
1253pub struct SirGrid {
1254    pub grid: Vec<SirCell>,
1255    pub width: usize,
1256    pub height: usize,
1257    pub beta: f64,
1258    pub gamma: f64,
1259    pub diffusion: f64,
1260    pub time: f64,
1261}
1262
1263impl SirGrid {
1264    pub fn new(width: usize, height: usize, beta: f64, gamma: f64) -> Self {
1265        let n = width * height;
1266        let cell = SirCell { s: 1.0, i: 0.0, r: 0.0 };
1267        Self { grid: vec![cell; n], width, height, beta, gamma, diffusion: 0.1, time: 0.0 }
1268    }
1269
1270    pub fn seed(&mut self, x: usize, y: usize, fraction: f64) {
1271        if x < self.width && y < self.height {
1272            let i = y * self.width + x;
1273            self.grid[i].i = fraction;
1274            self.grid[i].s = (1.0 - fraction).max(0.0);
1275        }
1276    }
1277
1278    fn idx(&self, x: usize, y: usize) -> usize { y * self.width + x }
1279
1280    pub fn step(&mut self, dt: f64) {
1281        let old = self.grid.clone();
1282        for y in 0..self.height {
1283            for x in 0..self.width {
1284                let i = self.idx(x, y);
1285                let cell = old[i];
1286                let force = self.beta * cell.s * cell.i;
1287                // Diffusion of infected cells
1288                let xm = if x == 0 { self.width - 1 } else { x - 1 };
1289                let xp = if x == self.width - 1 { 0 } else { x + 1 };
1290                let ym = if y == 0 { self.height - 1 } else { y - 1 };
1291                let yp = if y == self.height - 1 { 0 } else { y + 1 };
1292                let lap_i = old[self.idx(xm, y)].i + old[self.idx(xp, y)].i
1293                    + old[self.idx(x, ym)].i + old[self.idx(x, yp)].i - 4.0 * cell.i;
1294                self.grid[i].s = (cell.s - dt * force).max(0.0);
1295                self.grid[i].i = (cell.i + dt * (force - self.gamma * cell.i + self.diffusion * lap_i)).max(0.0);
1296                self.grid[i].r = (cell.r + dt * self.gamma * cell.i).max(0.0);
1297            }
1298        }
1299        self.time += dt;
1300    }
1301
1302    pub fn total_infected(&self) -> f64 {
1303        self.grid.iter().map(|c| c.i).sum()
1304    }
1305}
1306
1307// ============================================================
1308// TRAFFIC SIMULATION
1309// ============================================================
1310
1311/// Vehicle in the IDM model.
1312#[derive(Clone, Debug)]
1313pub struct Vehicle {
1314    pub pos: f64,
1315    pub speed: f64,
1316    pub length: f64,
1317    pub desired_speed: f64,
1318    pub accel: f64,
1319    pub decel: f64,
1320}
1321
1322/// Nagel-Schreckenberg cellular automaton traffic model.
1323#[derive(Clone, Debug)]
1324pub struct NagelSchreckenberg {
1325    pub road: Vec<Option<usize>>,   // cell index → vehicle index or None
1326    pub vehicles: Vec<(usize, u32)>, // (cell_pos, speed) for each vehicle
1327    pub road_length: usize,
1328    pub v_max: u32,
1329    pub p_brake: f64,               // random braking probability
1330    rng_state: u64,
1331}
1332
1333impl NagelSchreckenberg {
1334    pub fn new(road_length: usize, v_max: u32, p_brake: f64, density: f64, seed: u64) -> Self {
1335        let mut road = vec![None; road_length];
1336        let mut vehicles = Vec::new();
1337        let mut rng = seed;
1338        let n_vehicles = (density * road_length as f64) as usize;
1339        let mut positions: Vec<usize> = (0..road_length).collect();
1340        // Shuffle positions
1341        for i in (1..road_length).rev() {
1342            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
1343            let j = (rng >> 33) as usize % (i + 1);
1344            positions.swap(i, j);
1345        }
1346        for k in 0..n_vehicles.min(road_length) {
1347            let pos = positions[k];
1348            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
1349            let spd = (rng >> 33) as u32 % (v_max + 1);
1350            road[pos] = Some(k);
1351            vehicles.push((pos, spd));
1352        }
1353        Self { road, vehicles, road_length, v_max, p_brake, rng_state: rng }
1354    }
1355
1356    fn rand(&mut self) -> f64 {
1357        self.rng_state = self.rng_state.wrapping_mul(6364136223846793005).wrapping_add(1);
1358        (self.rng_state >> 33) as f64 / (u32::MAX as f64)
1359    }
1360
1361    pub fn step(&mut self) {
1362        let n_veh = self.vehicles.len();
1363        let road_len = self.road_length;
1364
1365        // Phase 1: Acceleration
1366        for v in &mut self.vehicles {
1367            if v.1 < self.v_max { v.1 += 1; }
1368        }
1369        // Phase 2: Braking (maintain gap)
1370        let positions: Vec<usize> = self.vehicles.iter().map(|v| v.0).collect();
1371        for (k, v) in self.vehicles.iter_mut().enumerate() {
1372            // Find gap to next vehicle
1373            let mut gap = road_len; // maximum
1374            for j in 1..=road_len {
1375                let next_pos = (v.0 + j) % road_len;
1376                if self.road[next_pos].is_some() { gap = j - 1; break; }
1377            }
1378            if v.1 as usize > gap { v.1 = gap as u32; }
1379        }
1380        // Phase 3: Random braking
1381        let brakes: Vec<f64> = (0..self.vehicles.len()).map(|_| self.rand()).collect();
1382        for (i, v) in self.vehicles.iter_mut().enumerate() {
1383            if v.1 > 0 && brakes[i] < self.p_brake { v.1 -= 1; }
1384        }
1385        // Phase 4: Move
1386        let mut new_road = vec![None; road_len];
1387        for (k, v) in self.vehicles.iter_mut().enumerate() {
1388            self.road[v.0] = None;
1389            v.0 = (v.0 + v.1 as usize) % road_len;
1390            new_road[v.0] = Some(k);
1391        }
1392        self.road = new_road;
1393    }
1394
1395    pub fn flow_rate(&self) -> f64 {
1396        let total_speed: f64 = self.vehicles.iter().map(|v| v.1 as f64).sum();
1397        total_speed / self.road_length as f64
1398    }
1399
1400    pub fn density(&self) -> f64 {
1401        self.vehicles.len() as f64 / self.road_length as f64
1402    }
1403
1404    pub fn average_speed(&self) -> f64 {
1405        if self.vehicles.is_empty() { return 0.0; }
1406        self.vehicles.iter().map(|v| v.1 as f64).sum::<f64>() / self.vehicles.len() as f64
1407    }
1408}
1409
1410/// Intelligent Driver Model (IDM) parameters.
1411#[derive(Clone, Debug)]
1412pub struct IntelligentDriverModel {
1413    pub t_desired: f64,   // desired time headway (s)
1414    pub a_desired: f64,   // max acceleration (m/s²)
1415    pub b_desired: f64,   // comfortable deceleration (m/s²)
1416    pub v_desired: f64,   // desired speed (m/s)
1417    pub s_min: f64,        // minimum gap (m)
1418}
1419
1420impl IntelligentDriverModel {
1421    pub fn new() -> Self {
1422        Self {
1423            t_desired: 1.5,
1424            a_desired: 1.4,
1425            b_desired: 2.0,
1426            v_desired: 33.33,   // ~120 km/h
1427            s_min: 2.0,
1428        }
1429    }
1430
1431    /// Compute acceleration for a vehicle given:
1432    /// `v` — current speed, `delta_v` — speed difference to leader (v - v_leader),
1433    /// `s` — gap to leader.
1434    pub fn acceleration(&self, v: f64, delta_v: f64, s: f64) -> f64 {
1435        let s_star = self.s_min
1436            + (v * self.t_desired
1437                + v * delta_v / (2.0 * (self.a_desired * self.b_desired).sqrt()))
1438            .max(0.0);
1439        let free_term = (v / self.v_desired).powi(4);
1440        let interaction_term = (s_star / s.max(0.001)).powi(2);
1441        self.a_desired * (1.0 - free_term - interaction_term)
1442    }
1443}
1444
1445impl Default for IntelligentDriverModel {
1446    fn default() -> Self { Self::new() }
1447}
1448
1449// ============================================================
1450// TESTS
1451// ============================================================
1452
1453#[cfg(test)]
1454mod tests {
1455    use super::*;
1456
1457    #[test]
1458    fn test_game_of_life_step() {
1459        let mut gol = GameOfLife::new(20, 20);
1460        gol.place_glider(1, 1);
1461        let initial_count = gol.count_living();
1462        assert_eq!(initial_count, 5);
1463        gol.step();
1464        // Glider should still have 5 live cells after one step
1465        assert_eq!(gol.count_living(), 5);
1466    }
1467
1468    #[test]
1469    fn test_game_of_life_still_life() {
1470        // A 2x2 block is a still life
1471        let mut gol = GameOfLife::new(10, 10);
1472        gol.grid[1][1] = true; gol.grid[1][2] = true;
1473        gol.grid[2][1] = true; gol.grid[2][2] = true;
1474        assert!(gol.is_stable());
1475    }
1476
1477    #[test]
1478    fn test_wolfram_rule30() {
1479        let ca = Totalistic1D::new(30);
1480        let init: Vec<bool> = (0..11).map(|i| i == 5).collect();
1481        let result = ca.evolve(init, 5);
1482        assert_eq!(result.len(), 6);
1483    }
1484
1485    #[test]
1486    fn test_wolfram_rule110() {
1487        let ca = Totalistic1D::new(110);
1488        let init = vec![false, false, false, false, true, false, false, false, false];
1489        let result = ca.evolve(init, 4);
1490        assert_eq!(result.len(), 5);
1491    }
1492
1493    #[test]
1494    fn test_langtons_ant_steps() {
1495        let mut ant = LangtonsAnt::new();
1496        ant.step_n(100);
1497        assert_eq!(ant.steps, 100);
1498        // After 100 steps, should have some black cells
1499        assert!(ant.count_black() > 0);
1500    }
1501
1502    #[test]
1503    fn test_langtons_ant_highway() {
1504        // After 10000 steps Langton's ant enters the highway pattern
1505        let mut ant = LangtonsAnt::new();
1506        ant.step_n(10000);
1507        assert_eq!(ant.steps, 10000);
1508    }
1509
1510    #[test]
1511    fn test_nbody_basic() {
1512        let bodies = vec![
1513            Body { position: Vec3::new(0.0, 0.0, 0.0), velocity: Vec3::zero(), mass: 1e10, radius: 1.0 },
1514            Body { position: Vec3::new(100.0, 0.0, 0.0), velocity: Vec3::zero(), mass: 1e10, radius: 1.0 },
1515        ];
1516        let mut sim = NBodySimulation::new(bodies, 1.0);
1517        direct_gravity_step(&mut sim);
1518        // Bodies should move toward each other
1519        assert!(sim.bodies[0].velocity.x > 0.0);
1520        assert!(sim.bodies[1].velocity.x < 0.0);
1521    }
1522
1523    #[test]
1524    fn test_leapfrog_step() {
1525        let bodies = vec![
1526            Body { position: Vec3::new(0.0, 0.0, 0.0), velocity: Vec3::new(0.0, 1e-3, 0.0), mass: 1e12, radius: 1.0 },
1527            Body { position: Vec3::new(1000.0, 0.0, 0.0), velocity: Vec3::new(0.0, -1e-3, 0.0), mass: 1e12, radius: 1.0 },
1528        ];
1529        let mut sim = NBodySimulation::new(bodies, 0.1);
1530        for _ in 0..10 { leapfrog_step(&mut sim); }
1531        assert!((sim.time - 1.0).abs() < 1e-10);
1532    }
1533
1534    #[test]
1535    fn test_solve_kepler() {
1536        // Circular orbit: e=0, M=1.0 => E=1.0
1537        let e_anom = solve_kepler_equation(1.0, 0.0);
1538        assert!((e_anom - 1.0).abs() < 1e-10);
1539    }
1540
1541    #[test]
1542    fn test_orbital_round_trip() {
1543        let r = Vec3::new(6371e3 + 400e3, 0.0, 0.0); // LEO
1544        let v = Vec3::new(0.0, 7.66e3, 0.0); // approximately circular
1545        let mu = 3.986e14;
1546        let elems = from_state_vectors(r, v, mu);
1547        let (r2, v2) = to_state_vectors(&elems, mu);
1548        assert!((r.x - r2.x).abs() < 1.0, "r.x mismatch");
1549        assert!((v.y - v2.y).abs() < 1.0, "v.y mismatch");
1550    }
1551
1552    #[test]
1553    fn test_gray_scott_step() {
1554        let mut gs = GrayScott::new(20, 20, 0.055, 0.062);
1555        gs.seed_center();
1556        for _ in 0..10 { gs.step(1.0); }
1557        // Just check no NaN or out-of-range
1558        assert!(gs.u.iter().all(|&x| x >= 0.0 && x <= 1.0));
1559        assert!(gs.v.iter().all(|&x| x >= 0.0 && x <= 1.0));
1560    }
1561
1562    #[test]
1563    fn test_fitzhugh_nagumo_step() {
1564        let mut fhn = FitzHughNagumo::new(10, 10);
1565        fhn.stimulate(5, 5, 1);
1566        for _ in 0..20 { fhn.step(0.05); }
1567        // Check finite values
1568        assert!(fhn.v.iter().all(|v| v.is_finite()));
1569    }
1570
1571    #[test]
1572    fn test_sir_model() {
1573        let mut sir = SirModel::new(999.0, 1.0, 1000.0, 0.3, 0.1);
1574        let r0 = sir.basic_reproduction_number();
1575        assert!((r0 - 3.0).abs() < 1e-10);
1576        for _ in 0..100 { sir.step(1.0); }
1577        // Epidemic should have spread
1578        assert!(sir.r > 10.0);
1579    }
1580
1581    #[test]
1582    fn test_sir_herd_immunity() {
1583        let sir = SirModel::new(999.0, 1.0, 1000.0, 0.3, 0.1);
1584        let hit = sir.herd_immunity_threshold();
1585        assert!((hit - 2.0 / 3.0).abs() < 1e-10);
1586    }
1587
1588    #[test]
1589    fn test_seird_model() {
1590        let mut seird = SeirdModel::new(9990.0, 5.0, 5.0, 10000.0, 0.3, 0.2, 0.1, 0.01);
1591        for _ in 0..50 { seird.step(1.0); }
1592        assert!(seird.r > 0.0);
1593        assert!(seird.d > 0.0);
1594    }
1595
1596    #[test]
1597    fn test_nagel_schreckenberg() {
1598        let mut ns = NagelSchreckenberg::new(100, 5, 0.3, 0.3, 42);
1599        for _ in 0..20 { ns.step(); }
1600        assert!(ns.average_speed() >= 0.0);
1601        assert!(ns.flow_rate() >= 0.0);
1602        assert!(ns.density() >= 0.0 && ns.density() <= 1.0);
1603    }
1604
1605    #[test]
1606    fn test_idm_acceleration() {
1607        let idm = IntelligentDriverModel::new();
1608        // Free flow — no leader in front, large gap
1609        let a_free = idm.acceleration(20.0, 0.0, 1000.0);
1610        assert!(a_free > 0.0, "should accelerate in free flow");
1611        // Braking — small gap and leader is slower
1612        let a_brake = idm.acceleration(20.0, 10.0, 5.0);
1613        assert!(a_brake < 0.0, "should decelerate when too close");
1614    }
1615
1616    #[test]
1617    fn test_bz_reaction() {
1618        let params = BzParams::default();
1619        let mut bz = BelousovZhabotinsky::new(10, 10, params);
1620        for _ in 0..5 { bz.step(0.01); }
1621        // All concentrations should remain in [0,1]
1622        for cell in &bz.concentrations {
1623            for &c in cell { assert!(c >= 0.0 && c <= 1.0, "concentration out of bounds: {}", c); }
1624        }
1625    }
1626
1627    #[test]
1628    fn test_wireworld_step() {
1629        let mut ww = WireWorld::new(10, 5);
1630        ww.set(0, 2, WireWorldCell::Conductor);
1631        ww.set(1, 2, WireWorldCell::ElectronHead);
1632        ww.set(2, 2, WireWorldCell::Conductor);
1633        ww.step();
1634        assert_eq!(ww.grid[2][1], WireWorldCell::ElectronTail);
1635        assert_eq!(ww.grid[2][2], WireWorldCell::ElectronHead);
1636    }
1637
1638    #[test]
1639    fn test_forest_fire_step() {
1640        let mut ff = ForestFire::new(20, 20, 0.1, 0.001, 42);
1641        // Fill with trees
1642        for row in ff.grid.iter_mut() {
1643            for cell in row.iter_mut() { *cell = ForestCell::Tree; }
1644        }
1645        ff.grid[10][10] = ForestCell::Burning;
1646        ff.step();
1647        // Some neighboring cells should have caught fire
1648        let burning = ff.count_burning();
1649        assert!(burning > 0);
1650    }
1651
1652    #[test]
1653    fn test_brians_brain() {
1654        let mut bb = BriansBrain::new(10, 10);
1655        bb.grid[5][5] = BrianCell::On;
1656        bb.step();
1657        assert_eq!(bb.grid[5][5], BrianCell::Dying);
1658        bb.step();
1659        assert_eq!(bb.grid[5][5], BrianCell::Off);
1660    }
1661
1662    #[test]
1663    fn test_sir_grid() {
1664        let mut sg = SirGrid::new(20, 20, 0.3, 0.05);
1665        sg.seed(10, 10, 0.1);
1666        for _ in 0..50 { sg.step(1.0); }
1667        assert!(sg.total_infected() > 0.0);
1668    }
1669
1670    #[test]
1671    fn test_vec3_ops() {
1672        let a = Vec3::new(1.0, 0.0, 0.0);
1673        let b = Vec3::new(0.0, 1.0, 0.0);
1674        let c = a.cross(b);
1675        assert!((c.z - 1.0).abs() < 1e-10);
1676        assert!((a.dot(b)).abs() < 1e-10);
1677    }
1678}