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 if ecc > 1e-12 {
812        // Equatorial orbit: measure argument of periapsis from x-axis
813        let mut omega = (e_vec.x / ecc).clamp(-1.0, 1.0).acos();
814        if e_vec.y < 0.0 { omega = 2.0 * PI - omega; }
815        omega
816    } else { 0.0 };
817
818    let nu = if ecc > 1e-12 {
819        let mut nu = (e_vec.dot(r) / (ecc * r_len)).clamp(-1.0, 1.0).acos();
820        if r.dot(v) < 0.0 { nu = 2.0 * PI - nu; }
821        nu
822    } else { 0.0 };
823
824    OrbitalElements {
825        semi_major_axis: a,
826        eccentricity: ecc,
827        inclination: i,
828        lan,
829        arg_periapsis: arg_p,
830        true_anomaly: nu,
831    }
832}
833
834/// Convert orbital elements to state vectors.
835pub fn to_state_vectors(elems: &OrbitalElements, mu: f64) -> (Vec3, Vec3) {
836    let a = elems.semi_major_axis;
837    let e = elems.eccentricity;
838    let i = elems.inclination;
839    let lan = elems.lan;
840    let omega = elems.arg_periapsis;
841    let nu = elems.true_anomaly;
842
843    let p = a * (1.0 - e * e);
844    let r_perifocal = p / (1.0 + e * nu.cos());
845
846    let r_peri = Vec3::new(r_perifocal * nu.cos(), r_perifocal * nu.sin(), 0.0);
847    let v_peri = Vec3::new(
848        -(mu / p).sqrt() * nu.sin(),
849        (mu / p).sqrt() * (e + nu.cos()),
850        0.0,
851    );
852
853    // Rotation matrices
854    let rx = |theta: f64, v: Vec3| -> Vec3 {
855        Vec3::new(v.x, v.y * theta.cos() - v.z * theta.sin(), v.y * theta.sin() + v.z * theta.cos())
856    };
857    let rz = |theta: f64, v: Vec3| -> Vec3 {
858        Vec3::new(v.x * theta.cos() - v.y * theta.sin(), v.x * theta.sin() + v.y * theta.cos(), v.z)
859    };
860
861    let r_rot = rz(-lan, rx(-i, rz(-omega, r_peri)));
862    let v_rot = rz(-lan, rx(-i, rz(-omega, v_peri)));
863
864    (r_rot, v_rot)
865}
866
867/// Solve Kepler's equation M = E - e*sin(E) for eccentric anomaly E.
868pub fn solve_kepler_equation(m_anom: f64, e: f64) -> f64 {
869    let mut e_anom = if e < 0.8 { m_anom } else { PI };
870    for _ in 0..100 {
871        let f = e_anom - e * e_anom.sin() - m_anom;
872        let df = 1.0 - e * e_anom.cos();
873        if df.abs() < 1e-12 { break; }
874        let delta = f / df;
875        e_anom -= delta;
876        if delta.abs() < 1e-12 { break; }
877    }
878    e_anom
879}
880
881// ============================================================
882// REACTION-DIFFUSION
883// ============================================================
884
885/// Gray-Scott reaction-diffusion system.
886/// Models Turing pattern formation.
887#[derive(Clone, Debug)]
888pub struct GrayScott {
889    pub u: Vec<f64>,
890    pub v: Vec<f64>,
891    pub width: usize,
892    pub height: usize,
893    pub feed: f64,
894    pub kill: f64,
895    pub du: f64,
896    pub dv: f64,
897}
898
899/// Preset parameters for Gray-Scott system.
900#[derive(Clone, Copy, Debug)]
901pub struct GrayScottPreset {
902    pub feed: f64,
903    pub kill: f64,
904    pub name: &'static str,
905}
906
907impl GrayScott {
908    pub fn new(width: usize, height: usize, feed: f64, kill: f64) -> Self {
909        let n = width * height;
910        Self {
911            u: vec![1.0; n],
912            v: vec![0.0; n],
913            width, height, feed, kill,
914            du: 0.2,
915            dv: 0.1,
916        }
917    }
918
919    pub fn seed_center(&mut self) {
920        let cx = self.width / 2;
921        let cy = self.height / 2;
922        let r = 5.min(self.width.min(self.height) / 4);
923        for y in (cy.saturating_sub(r))..(cy + r).min(self.height) {
924            for x in (cx.saturating_sub(r))..(cx + r).min(self.width) {
925                let idx = y * self.width + x;
926                self.u[idx] = 0.5;
927                self.v[idx] = 0.25;
928            }
929        }
930    }
931
932    #[inline]
933    fn idx(&self, x: usize, y: usize) -> usize { y * self.width + x }
934
935    fn laplacian_u(&self, x: usize, y: usize) -> f64 {
936        let cx = x;
937        let cy = y;
938        let xm = if cx == 0 { self.width - 1 } else { cx - 1 };
939        let xp = if cx == self.width - 1 { 0 } else { cx + 1 };
940        let ym = if cy == 0 { self.height - 1 } else { cy - 1 };
941        let yp = if cy == self.height - 1 { 0 } else { cy + 1 };
942        self.u[self.idx(xm, cy)] + self.u[self.idx(xp, cy)]
943            + self.u[self.idx(cx, ym)] + self.u[self.idx(cx, yp)]
944            - 4.0 * self.u[self.idx(cx, cy)]
945    }
946
947    fn laplacian_v(&self, x: usize, y: usize) -> f64 {
948        let cx = x; let cy = y;
949        let xm = if cx == 0 { self.width - 1 } else { cx - 1 };
950        let xp = if cx == self.width - 1 { 0 } else { cx + 1 };
951        let ym = if cy == 0 { self.height - 1 } else { cy - 1 };
952        let yp = if cy == self.height - 1 { 0 } else { cy + 1 };
953        self.v[self.idx(xm, cy)] + self.v[self.idx(xp, cy)]
954            + self.v[self.idx(cx, ym)] + self.v[self.idx(cx, yp)]
955            - 4.0 * self.v[self.idx(cx, cy)]
956    }
957
958    pub fn step(&mut self, dt: f64) {
959        let n = self.width * self.height;
960        let mut u_new = vec![0.0f64; n];
961        let mut v_new = vec![0.0f64; n];
962        for y in 0..self.height {
963            for x in 0..self.width {
964                let i = self.idx(x, y);
965                let u = self.u[i];
966                let v = self.v[i];
967                let uvv = u * v * v;
968                u_new[i] = u + dt * (self.du * self.laplacian_u(x, y) - uvv + self.feed * (1.0 - u));
969                v_new[i] = v + dt * (self.dv * self.laplacian_v(x, y) + uvv - (self.feed + self.kill) * v);
970                u_new[i] = u_new[i].clamp(0.0, 1.0);
971                v_new[i] = v_new[i].clamp(0.0, 1.0);
972            }
973        }
974        self.u = u_new;
975        self.v = v_new;
976    }
977
978    /// Preset: mitosis (f=0.0367, k=0.0649)
979    pub fn preset_mitosis(width: usize, height: usize) -> Self {
980        Self::new(width, height, 0.0367, 0.0649)
981    }
982
983    /// Preset: coral (f=0.0545, k=0.062)
984    pub fn preset_coral(width: usize, height: usize) -> Self {
985        Self::new(width, height, 0.0545, 0.062)
986    }
987
988    /// Preset: worms (f=0.078, k=0.061)
989    pub fn preset_worms(width: usize, height: usize) -> Self {
990        Self::new(width, height, 0.078, 0.061)
991    }
992
993    /// Preset: maze (f=0.029, k=0.057)
994    pub fn preset_maze(width: usize, height: usize) -> Self {
995        Self::new(width, height, 0.029, 0.057)
996    }
997
998    /// Preset: solitons (f=0.03, k=0.06)
999    pub fn preset_solitons(width: usize, height: usize) -> Self {
1000        Self::new(width, height, 0.03, 0.06)
1001    }
1002
1003    /// Preset: unstable (f=0.02, k=0.055)
1004    pub fn preset_unstable(width: usize, height: usize) -> Self {
1005        Self::new(width, height, 0.02, 0.055)
1006    }
1007}
1008
1009// ============================================================
1010// FITZHUGH-NAGUMO
1011// ============================================================
1012
1013/// FitzHugh-Nagumo excitable medium — models action potentials and spiral waves.
1014#[derive(Clone, Debug)]
1015pub struct FitzHughNagumo {
1016    pub v: Vec<f64>,   // fast variable (voltage)
1017    pub w: Vec<f64>,   // slow recovery variable
1018    pub width: usize,
1019    pub height: usize,
1020    pub a: f64,        // threshold parameter
1021    pub b: f64,        // recovery coupling
1022    pub tau: f64,      // time scale separation
1023    pub eps: f64,      // diffusion
1024}
1025
1026impl FitzHughNagumo {
1027    pub fn new(width: usize, height: usize) -> Self {
1028        let n = width * height;
1029        Self {
1030            v: vec![0.0; n],
1031            w: vec![0.0; n],
1032            width, height,
1033            a: 0.7, b: 0.8, tau: 12.5, eps: 0.1,
1034        }
1035    }
1036
1037    pub fn stimulate(&mut self, x: usize, y: usize, radius: usize) {
1038        for dy in 0..=radius * 2 {
1039            for dx in 0..=radius * 2 {
1040                let nx = (x + dx).saturating_sub(radius);
1041                let ny = (y + dy).saturating_sub(radius);
1042                if nx < self.width && ny < self.height {
1043                    let i = ny * self.width + nx;
1044                    self.v[i] = 1.0;
1045                }
1046            }
1047        }
1048    }
1049
1050    fn laplacian(data: &[f64], x: usize, y: usize, w: usize, h: usize) -> f64 {
1051        let idx = |x: usize, y: usize| y * w + x;
1052        let xm = if x == 0 { w - 1 } else { x - 1 };
1053        let xp = if x == w - 1 { 0 } else { x + 1 };
1054        let ym = if y == 0 { h - 1 } else { y - 1 };
1055        let yp = if y == h - 1 { 0 } else { y + 1 };
1056        data[idx(xm, y)] + data[idx(xp, y)] + data[idx(x, ym)] + data[idx(x, yp)] - 4.0 * data[idx(x, y)]
1057    }
1058
1059    pub fn step(&mut self, dt: f64) {
1060        let n = self.width * self.height;
1061        let mut v_new = vec![0.0f64; n];
1062        let mut w_new = vec![0.0f64; n];
1063        for y in 0..self.height {
1064            for x in 0..self.width {
1065                let i = y * self.width + x;
1066                let v = self.v[i];
1067                let w = self.w[i];
1068                let lap_v = Self::laplacian(&self.v, x, y, self.width, self.height);
1069                let dv = v - v.powi(3) / 3.0 - w + self.eps * lap_v;
1070                let dw = (v + self.a - self.b * w) / self.tau;
1071                v_new[i] = v + dt * dv;
1072                w_new[i] = w + dt * dw;
1073            }
1074        }
1075        self.v = v_new;
1076        self.w = w_new;
1077    }
1078}
1079
1080// ============================================================
1081// BELOUSOV-ZHABOTINSKY
1082// ============================================================
1083
1084/// Parameters for the Belousov-Zhabotinsky oscillator.
1085#[derive(Clone, Debug)]
1086pub struct BzParams {
1087    pub alpha: f64,
1088    pub beta: f64,
1089    pub gamma: f64,
1090}
1091
1092impl Default for BzParams {
1093    fn default() -> Self { BzParams { alpha: 0.1, beta: 0.1, gamma: 0.1 } }
1094}
1095
1096/// Simplified 3-variable BZ reaction model on a 2D grid.
1097#[derive(Clone, Debug)]
1098pub struct BelousovZhabotinsky {
1099    /// Each cell stores 3 concentrations [u, v, w].
1100    pub concentrations: Vec<Vec<f64>>,
1101    pub width: usize,
1102    pub height: usize,
1103    pub params: BzParams,
1104}
1105
1106impl BelousovZhabotinsky {
1107    pub fn new(width: usize, height: usize, params: BzParams) -> Self {
1108        let n = width * height;
1109        // Random-ish initial concentrations
1110        let mut concentrations = vec![vec![0.0f64; 3]; n];
1111        let mut state = 12345u64;
1112        for row in &mut concentrations {
1113            for v in row.iter_mut() {
1114                state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
1115                *v = (state >> 33) as f64 / (u32::MAX as f64);
1116            }
1117        }
1118        Self { concentrations, width, height, params }
1119    }
1120
1121    fn laplacian_conc(data: &[Vec<f64>], comp: usize, x: usize, y: usize, w: usize, h: usize) -> f64 {
1122        let idx = |x: usize, y: usize| y * w + x;
1123        let xm = if x == 0 { w - 1 } else { x - 1 };
1124        let xp = if x == w - 1 { 0 } else { x + 1 };
1125        let ym = if y == 0 { h - 1 } else { y - 1 };
1126        let yp = if y == h - 1 { 0 } else { y + 1 };
1127        data[idx(xm, y)][comp] + data[idx(xp, y)][comp]
1128            + data[idx(x, ym)][comp] + data[idx(x, yp)][comp]
1129            - 4.0 * data[idx(x, y)][comp]
1130    }
1131
1132    pub fn step(&mut self, dt: f64) {
1133        let n = self.width * self.height;
1134        let mut new_c = self.concentrations.clone();
1135        let alpha = self.params.alpha;
1136        let beta = self.params.beta;
1137        let gamma = self.params.gamma;
1138        for y in 0..self.height {
1139            for x in 0..self.width {
1140                let i = y * self.width + x;
1141                let u = self.concentrations[i][0];
1142                let v = self.concentrations[i][1];
1143                let w = self.concentrations[i][2];
1144                let lap_u = Self::laplacian_conc(&self.concentrations, 0, x, y, self.width, self.height);
1145                let lap_v = Self::laplacian_conc(&self.concentrations, 1, x, y, self.width, self.height);
1146                let lap_w = Self::laplacian_conc(&self.concentrations, 2, x, y, self.width, self.height);
1147                // Oregonator-inspired simplified ODE
1148                let du = u * (1.0 - u - alpha * v / (u + beta)) + 0.1 * lap_u;
1149                let dv = u - v + 0.05 * lap_v;
1150                let dw = gamma * (u - w) + 0.02 * lap_w;
1151                new_c[i][0] = (u + dt * du).clamp(0.0, 1.0);
1152                new_c[i][1] = (v + dt * dv).clamp(0.0, 1.0);
1153                new_c[i][2] = (w + dt * dw).clamp(0.0, 1.0);
1154            }
1155        }
1156        self.concentrations = new_c;
1157    }
1158}
1159
1160// ============================================================
1161// EPIDEMIOLOGICAL MODELS
1162// ============================================================
1163
1164/// Susceptible-Infected-Recovered (SIR) model.
1165#[derive(Clone, Debug)]
1166pub struct SirModel {
1167    pub s: f64,     // susceptible fraction
1168    pub i: f64,     // infected fraction
1169    pub r: f64,     // recovered fraction
1170    pub n: f64,     // total population
1171    pub beta: f64,  // transmission rate
1172    pub gamma: f64, // recovery rate
1173    pub time: f64,
1174}
1175
1176impl SirModel {
1177    pub fn new(s0: f64, i0: f64, n: f64, beta: f64, gamma: f64) -> Self {
1178        let r0 = n - s0 - i0;
1179        Self { s: s0, i: i0, r: r0.max(0.0), n, beta, gamma, time: 0.0 }
1180    }
1181
1182    pub fn step(&mut self, dt: f64) {
1183        let ds = -self.beta * self.s * self.i / self.n;
1184        let di = self.beta * self.s * self.i / self.n - self.gamma * self.i;
1185        let dr = self.gamma * self.i;
1186        self.s = (self.s + dt * ds).max(0.0);
1187        self.i = (self.i + dt * di).max(0.0);
1188        self.r = (self.r + dt * dr).max(0.0);
1189        self.time += dt;
1190    }
1191
1192    pub fn basic_reproduction_number(&self) -> f64 {
1193        self.beta / self.gamma
1194    }
1195
1196    pub fn time_to_peak(&self) -> f64 {
1197        // Approximate: t_peak ~ (1/(beta-gamma)) * ln(beta*S0/(gamma*N))
1198        let r0 = self.basic_reproduction_number();
1199        if r0 <= 1.0 { return f64::INFINITY; }
1200        let beta = self.beta; let gamma = self.gamma;
1201        (1.0 / (beta - gamma)) * (beta * self.s / (gamma * self.n)).ln().abs()
1202    }
1203
1204    pub fn herd_immunity_threshold(&self) -> f64 {
1205        1.0 - 1.0 / self.basic_reproduction_number()
1206    }
1207}
1208
1209/// SEIRD model with Exposed and Deceased compartments.
1210#[derive(Clone, Debug)]
1211pub struct SeirdModel {
1212    pub s: f64,      // susceptible
1213    pub e: f64,      // exposed (latent)
1214    pub i: f64,      // infectious
1215    pub r: f64,      // recovered
1216    pub d: f64,      // deceased
1217    pub n: f64,      // total population
1218    pub beta: f64,   // transmission rate
1219    pub sigma: f64,  // rate E→I (1/incubation period)
1220    pub gamma: f64,  // recovery rate
1221    pub delta: f64,  // mortality rate
1222    pub time: f64,
1223}
1224
1225impl SeirdModel {
1226    pub fn new(s0: f64, e0: f64, i0: f64, n: f64, beta: f64, sigma: f64, gamma: f64, delta: f64) -> Self {
1227        Self { s: s0, e: e0, i: i0, r: 0.0, d: 0.0, n, beta, sigma, gamma, delta, time: 0.0 }
1228    }
1229
1230    pub fn step(&mut self, dt: f64) {
1231        let force = self.beta * self.s * self.i / self.n;
1232        let ds = -force;
1233        let de = force - self.sigma * self.e;
1234        let di = self.sigma * self.e - (self.gamma + self.delta) * self.i;
1235        let dr = self.gamma * self.i;
1236        let dd = self.delta * self.i;
1237        self.s = (self.s + dt * ds).max(0.0);
1238        self.e = (self.e + dt * de).max(0.0);
1239        self.i = (self.i + dt * di).max(0.0);
1240        self.r = (self.r + dt * dr).max(0.0);
1241        self.d = (self.d + dt * dd).max(0.0);
1242        self.time += dt;
1243    }
1244
1245    pub fn total_active(&self) -> f64 { self.s + self.e + self.i }
1246}
1247
1248/// Per-cell state for spatial SIR model.
1249#[derive(Clone, Copy, Debug, Default)]
1250pub struct SirCell {
1251    pub s: f64,
1252    pub i: f64,
1253    pub r: f64,
1254}
1255
1256/// Spatial SIR model on a 2D grid.
1257#[derive(Clone, Debug)]
1258pub struct SirGrid {
1259    pub grid: Vec<SirCell>,
1260    pub width: usize,
1261    pub height: usize,
1262    pub beta: f64,
1263    pub gamma: f64,
1264    pub diffusion: f64,
1265    pub time: f64,
1266}
1267
1268impl SirGrid {
1269    pub fn new(width: usize, height: usize, beta: f64, gamma: f64) -> Self {
1270        let n = width * height;
1271        let cell = SirCell { s: 1.0, i: 0.0, r: 0.0 };
1272        Self { grid: vec![cell; n], width, height, beta, gamma, diffusion: 0.1, time: 0.0 }
1273    }
1274
1275    pub fn seed(&mut self, x: usize, y: usize, fraction: f64) {
1276        if x < self.width && y < self.height {
1277            let i = y * self.width + x;
1278            self.grid[i].i = fraction;
1279            self.grid[i].s = (1.0 - fraction).max(0.0);
1280        }
1281    }
1282
1283    fn idx(&self, x: usize, y: usize) -> usize { y * self.width + x }
1284
1285    pub fn step(&mut self, dt: f64) {
1286        let old = self.grid.clone();
1287        for y in 0..self.height {
1288            for x in 0..self.width {
1289                let i = self.idx(x, y);
1290                let cell = old[i];
1291                let force = self.beta * cell.s * cell.i;
1292                // Diffusion of infected cells
1293                let xm = if x == 0 { self.width - 1 } else { x - 1 };
1294                let xp = if x == self.width - 1 { 0 } else { x + 1 };
1295                let ym = if y == 0 { self.height - 1 } else { y - 1 };
1296                let yp = if y == self.height - 1 { 0 } else { y + 1 };
1297                let lap_i = old[self.idx(xm, y)].i + old[self.idx(xp, y)].i
1298                    + old[self.idx(x, ym)].i + old[self.idx(x, yp)].i - 4.0 * cell.i;
1299                self.grid[i].s = (cell.s - dt * force).max(0.0);
1300                self.grid[i].i = (cell.i + dt * (force - self.gamma * cell.i + self.diffusion * lap_i)).max(0.0);
1301                self.grid[i].r = (cell.r + dt * self.gamma * cell.i).max(0.0);
1302            }
1303        }
1304        self.time += dt;
1305    }
1306
1307    pub fn total_infected(&self) -> f64 {
1308        self.grid.iter().map(|c| c.i).sum()
1309    }
1310}
1311
1312// ============================================================
1313// TRAFFIC SIMULATION
1314// ============================================================
1315
1316/// Vehicle in the IDM model.
1317#[derive(Clone, Debug)]
1318pub struct Vehicle {
1319    pub pos: f64,
1320    pub speed: f64,
1321    pub length: f64,
1322    pub desired_speed: f64,
1323    pub accel: f64,
1324    pub decel: f64,
1325}
1326
1327/// Nagel-Schreckenberg cellular automaton traffic model.
1328#[derive(Clone, Debug)]
1329pub struct NagelSchreckenberg {
1330    pub road: Vec<Option<usize>>,   // cell index → vehicle index or None
1331    pub vehicles: Vec<(usize, u32)>, // (cell_pos, speed) for each vehicle
1332    pub road_length: usize,
1333    pub v_max: u32,
1334    pub p_brake: f64,               // random braking probability
1335    rng_state: u64,
1336}
1337
1338impl NagelSchreckenberg {
1339    pub fn new(road_length: usize, v_max: u32, p_brake: f64, density: f64, seed: u64) -> Self {
1340        let mut road = vec![None; road_length];
1341        let mut vehicles = Vec::new();
1342        let mut rng = seed;
1343        let n_vehicles = (density * road_length as f64) as usize;
1344        let mut positions: Vec<usize> = (0..road_length).collect();
1345        // Shuffle positions
1346        for i in (1..road_length).rev() {
1347            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
1348            let j = (rng >> 33) as usize % (i + 1);
1349            positions.swap(i, j);
1350        }
1351        for k in 0..n_vehicles.min(road_length) {
1352            let pos = positions[k];
1353            rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
1354            let spd = (rng >> 33) as u32 % (v_max + 1);
1355            road[pos] = Some(k);
1356            vehicles.push((pos, spd));
1357        }
1358        Self { road, vehicles, road_length, v_max, p_brake, rng_state: rng }
1359    }
1360
1361    fn rand(&mut self) -> f64 {
1362        self.rng_state = self.rng_state.wrapping_mul(6364136223846793005).wrapping_add(1);
1363        (self.rng_state >> 33) as f64 / (u32::MAX as f64)
1364    }
1365
1366    pub fn step(&mut self) {
1367        let n_veh = self.vehicles.len();
1368        let road_len = self.road_length;
1369
1370        // Phase 1: Acceleration
1371        for v in &mut self.vehicles {
1372            if v.1 < self.v_max { v.1 += 1; }
1373        }
1374        // Phase 2: Braking (maintain gap)
1375        let positions: Vec<usize> = self.vehicles.iter().map(|v| v.0).collect();
1376        for (k, v) in self.vehicles.iter_mut().enumerate() {
1377            // Find gap to next vehicle
1378            let mut gap = road_len; // maximum
1379            for j in 1..=road_len {
1380                let next_pos = (v.0 + j) % road_len;
1381                if self.road[next_pos].is_some() { gap = j - 1; break; }
1382            }
1383            if v.1 as usize > gap { v.1 = gap as u32; }
1384        }
1385        // Phase 3: Random braking
1386        let brakes: Vec<f64> = (0..self.vehicles.len()).map(|_| self.rand()).collect();
1387        for (i, v) in self.vehicles.iter_mut().enumerate() {
1388            if v.1 > 0 && brakes[i] < self.p_brake { v.1 -= 1; }
1389        }
1390        // Phase 4: Move
1391        let mut new_road = vec![None; road_len];
1392        for (k, v) in self.vehicles.iter_mut().enumerate() {
1393            self.road[v.0] = None;
1394            v.0 = (v.0 + v.1 as usize) % road_len;
1395            new_road[v.0] = Some(k);
1396        }
1397        self.road = new_road;
1398    }
1399
1400    pub fn flow_rate(&self) -> f64 {
1401        let total_speed: f64 = self.vehicles.iter().map(|v| v.1 as f64).sum();
1402        total_speed / self.road_length as f64
1403    }
1404
1405    pub fn density(&self) -> f64 {
1406        self.vehicles.len() as f64 / self.road_length as f64
1407    }
1408
1409    pub fn average_speed(&self) -> f64 {
1410        if self.vehicles.is_empty() { return 0.0; }
1411        self.vehicles.iter().map(|v| v.1 as f64).sum::<f64>() / self.vehicles.len() as f64
1412    }
1413}
1414
1415/// Intelligent Driver Model (IDM) parameters.
1416#[derive(Clone, Debug)]
1417pub struct IntelligentDriverModel {
1418    pub t_desired: f64,   // desired time headway (s)
1419    pub a_desired: f64,   // max acceleration (m/s²)
1420    pub b_desired: f64,   // comfortable deceleration (m/s²)
1421    pub v_desired: f64,   // desired speed (m/s)
1422    pub s_min: f64,        // minimum gap (m)
1423}
1424
1425impl IntelligentDriverModel {
1426    pub fn new() -> Self {
1427        Self {
1428            t_desired: 1.5,
1429            a_desired: 1.4,
1430            b_desired: 2.0,
1431            v_desired: 33.33,   // ~120 km/h
1432            s_min: 2.0,
1433        }
1434    }
1435
1436    /// Compute acceleration for a vehicle given:
1437    /// `v` — current speed, `delta_v` — speed difference to leader (v - v_leader),
1438    /// `s` — gap to leader.
1439    pub fn acceleration(&self, v: f64, delta_v: f64, s: f64) -> f64 {
1440        let s_star = self.s_min
1441            + (v * self.t_desired
1442                + v * delta_v / (2.0 * (self.a_desired * self.b_desired).sqrt()))
1443            .max(0.0);
1444        let free_term = (v / self.v_desired).powi(4);
1445        let interaction_term = (s_star / s.max(0.001)).powi(2);
1446        self.a_desired * (1.0 - free_term - interaction_term)
1447    }
1448}
1449
1450impl Default for IntelligentDriverModel {
1451    fn default() -> Self { Self::new() }
1452}
1453
1454// ============================================================
1455// TESTS
1456// ============================================================
1457
1458#[cfg(test)]
1459mod tests {
1460    use super::*;
1461
1462    #[test]
1463    fn test_game_of_life_step() {
1464        let mut gol = GameOfLife::new(20, 20);
1465        gol.place_glider(1, 1);
1466        let initial_count = gol.count_living();
1467        assert_eq!(initial_count, 5);
1468        gol.step();
1469        // Glider should still have 5 live cells after one step
1470        assert_eq!(gol.count_living(), 5);
1471    }
1472
1473    #[test]
1474    fn test_game_of_life_still_life() {
1475        // A 2x2 block is a still life
1476        let mut gol = GameOfLife::new(10, 10);
1477        gol.grid[1][1] = true; gol.grid[1][2] = true;
1478        gol.grid[2][1] = true; gol.grid[2][2] = true;
1479        assert!(gol.is_stable());
1480    }
1481
1482    #[test]
1483    fn test_wolfram_rule30() {
1484        let ca = Totalistic1D::new(30);
1485        let init: Vec<bool> = (0..11).map(|i| i == 5).collect();
1486        let result = ca.evolve(init, 5);
1487        assert_eq!(result.len(), 6);
1488    }
1489
1490    #[test]
1491    fn test_wolfram_rule110() {
1492        let ca = Totalistic1D::new(110);
1493        let init = vec![false, false, false, false, true, false, false, false, false];
1494        let result = ca.evolve(init, 4);
1495        assert_eq!(result.len(), 5);
1496    }
1497
1498    #[test]
1499    fn test_langtons_ant_steps() {
1500        let mut ant = LangtonsAnt::new();
1501        ant.step_n(100);
1502        assert_eq!(ant.steps, 100);
1503        // After 100 steps, should have some black cells
1504        assert!(ant.count_black() > 0);
1505    }
1506
1507    #[test]
1508    fn test_langtons_ant_highway() {
1509        // After 10000 steps Langton's ant enters the highway pattern
1510        let mut ant = LangtonsAnt::new();
1511        ant.step_n(10000);
1512        assert_eq!(ant.steps, 10000);
1513    }
1514
1515    #[test]
1516    fn test_nbody_basic() {
1517        let bodies = vec![
1518            Body { position: Vec3::new(0.0, 0.0, 0.0), velocity: Vec3::zero(), mass: 1e10, radius: 1.0 },
1519            Body { position: Vec3::new(100.0, 0.0, 0.0), velocity: Vec3::zero(), mass: 1e10, radius: 1.0 },
1520        ];
1521        let mut sim = NBodySimulation::new(bodies, 1.0);
1522        direct_gravity_step(&mut sim);
1523        // Bodies should move toward each other
1524        assert!(sim.bodies[0].velocity.x > 0.0);
1525        assert!(sim.bodies[1].velocity.x < 0.0);
1526    }
1527
1528    #[test]
1529    fn test_leapfrog_step() {
1530        let bodies = vec![
1531            Body { position: Vec3::new(0.0, 0.0, 0.0), velocity: Vec3::new(0.0, 1e-3, 0.0), mass: 1e12, radius: 1.0 },
1532            Body { position: Vec3::new(1000.0, 0.0, 0.0), velocity: Vec3::new(0.0, -1e-3, 0.0), mass: 1e12, radius: 1.0 },
1533        ];
1534        let mut sim = NBodySimulation::new(bodies, 0.1);
1535        for _ in 0..10 { leapfrog_step(&mut sim); }
1536        assert!((sim.time - 1.0).abs() < 1e-10);
1537    }
1538
1539    #[test]
1540    fn test_solve_kepler() {
1541        // Circular orbit: e=0, M=1.0 => E=1.0
1542        let e_anom = solve_kepler_equation(1.0, 0.0);
1543        assert!((e_anom - 1.0).abs() < 1e-10);
1544    }
1545
1546    #[test]
1547    fn test_orbital_round_trip() {
1548        let r = Vec3::new(6371e3 + 400e3, 0.0, 0.0); // LEO
1549        let v = Vec3::new(0.0, 7.66e3, 0.0); // approximately circular
1550        let mu = 3.986e14;
1551        let elems = from_state_vectors(r, v, mu);
1552        let (r2, v2) = to_state_vectors(&elems, mu);
1553        assert!((r.x - r2.x).abs() < 1.0, "r.x mismatch");
1554        assert!((v.y - v2.y).abs() < 1.0, "v.y mismatch");
1555    }
1556
1557    #[test]
1558    fn test_gray_scott_step() {
1559        let mut gs = GrayScott::new(20, 20, 0.055, 0.062);
1560        gs.seed_center();
1561        for _ in 0..10 { gs.step(1.0); }
1562        // Just check no NaN or out-of-range
1563        assert!(gs.u.iter().all(|&x| x >= 0.0 && x <= 1.0));
1564        assert!(gs.v.iter().all(|&x| x >= 0.0 && x <= 1.0));
1565    }
1566
1567    #[test]
1568    fn test_fitzhugh_nagumo_step() {
1569        let mut fhn = FitzHughNagumo::new(10, 10);
1570        fhn.stimulate(5, 5, 1);
1571        for _ in 0..20 { fhn.step(0.05); }
1572        // Check finite values
1573        assert!(fhn.v.iter().all(|v| v.is_finite()));
1574    }
1575
1576    #[test]
1577    fn test_sir_model() {
1578        let mut sir = SirModel::new(999.0, 1.0, 1000.0, 0.3, 0.1);
1579        let r0 = sir.basic_reproduction_number();
1580        assert!((r0 - 3.0).abs() < 1e-10);
1581        for _ in 0..100 { sir.step(1.0); }
1582        // Epidemic should have spread
1583        assert!(sir.r > 10.0);
1584    }
1585
1586    #[test]
1587    fn test_sir_herd_immunity() {
1588        let sir = SirModel::new(999.0, 1.0, 1000.0, 0.3, 0.1);
1589        let hit = sir.herd_immunity_threshold();
1590        assert!((hit - 2.0 / 3.0).abs() < 1e-10);
1591    }
1592
1593    #[test]
1594    fn test_seird_model() {
1595        let mut seird = SeirdModel::new(9990.0, 5.0, 5.0, 10000.0, 0.3, 0.2, 0.1, 0.01);
1596        for _ in 0..50 { seird.step(1.0); }
1597        assert!(seird.r > 0.0);
1598        assert!(seird.d > 0.0);
1599    }
1600
1601    #[test]
1602    fn test_nagel_schreckenberg() {
1603        let mut ns = NagelSchreckenberg::new(100, 5, 0.3, 0.3, 42);
1604        for _ in 0..20 { ns.step(); }
1605        assert!(ns.average_speed() >= 0.0);
1606        assert!(ns.flow_rate() >= 0.0);
1607        assert!(ns.density() >= 0.0 && ns.density() <= 1.0);
1608    }
1609
1610    #[test]
1611    fn test_idm_acceleration() {
1612        let idm = IntelligentDriverModel::new();
1613        // Free flow — no leader in front, large gap
1614        let a_free = idm.acceleration(20.0, 0.0, 1000.0);
1615        assert!(a_free > 0.0, "should accelerate in free flow");
1616        // Braking — small gap and leader is slower
1617        let a_brake = idm.acceleration(20.0, 10.0, 5.0);
1618        assert!(a_brake < 0.0, "should decelerate when too close");
1619    }
1620
1621    #[test]
1622    fn test_bz_reaction() {
1623        let params = BzParams::default();
1624        let mut bz = BelousovZhabotinsky::new(10, 10, params);
1625        for _ in 0..5 { bz.step(0.01); }
1626        // All concentrations should remain in [0,1]
1627        for cell in &bz.concentrations {
1628            for &c in cell { assert!(c >= 0.0 && c <= 1.0, "concentration out of bounds: {}", c); }
1629        }
1630    }
1631
1632    #[test]
1633    fn test_wireworld_step() {
1634        let mut ww = WireWorld::new(10, 5);
1635        ww.set(0, 2, WireWorldCell::Conductor);
1636        ww.set(1, 2, WireWorldCell::ElectronHead);
1637        ww.set(2, 2, WireWorldCell::Conductor);
1638        ww.step();
1639        assert_eq!(ww.grid[2][1], WireWorldCell::ElectronTail);
1640        assert_eq!(ww.grid[2][2], WireWorldCell::ElectronHead);
1641    }
1642
1643    #[test]
1644    fn test_forest_fire_step() {
1645        let mut ff = ForestFire::new(20, 20, 0.1, 0.001, 42);
1646        // Fill with trees
1647        for row in ff.grid.iter_mut() {
1648            for cell in row.iter_mut() { *cell = ForestCell::Tree; }
1649        }
1650        ff.grid[10][10] = ForestCell::Burning;
1651        ff.step();
1652        // Some neighboring cells should have caught fire
1653        let burning = ff.count_burning();
1654        assert!(burning > 0);
1655    }
1656
1657    #[test]
1658    fn test_brians_brain() {
1659        let mut bb = BriansBrain::new(10, 10);
1660        bb.grid[5][5] = BrianCell::On;
1661        bb.step();
1662        assert_eq!(bb.grid[5][5], BrianCell::Dying);
1663        bb.step();
1664        assert_eq!(bb.grid[5][5], BrianCell::Off);
1665    }
1666
1667    #[test]
1668    fn test_sir_grid() {
1669        let mut sg = SirGrid::new(20, 20, 0.3, 0.05);
1670        sg.seed(10, 10, 0.1);
1671        for _ in 0..50 { sg.step(1.0); }
1672        assert!(sg.total_infected() > 0.0);
1673    }
1674
1675    #[test]
1676    fn test_vec3_ops() {
1677        let a = Vec3::new(1.0, 0.0, 0.0);
1678        let b = Vec3::new(0.0, 1.0, 0.0);
1679        let c = a.cross(b);
1680        assert!((c.z - 1.0).abs() < 1e-10);
1681        assert!((a.dot(b)).abs() < 1e-10);
1682    }
1683}