Skip to main content

rill_patchbay/automaton/
cellular.rs

1//! # Cellular automata
2//!
3//! Signal generation based on cellular automata.
4//! Supports 1D and 2D cellular automata with various rules.
5
6use crate::engine::{Automaton, NoAction, Range, Time};
7use rill_core::traits::ParamValue;
8
9/// Type of cellular automaton
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum CellularType {
12    /// 1D cellular automaton (Wolfram rule)
13    OneDimensional,
14    /// 2D cellular automaton (Game of Life)
15    TwoDimensional,
16    /// Cyclic cellular automaton
17    Cyclic,
18}
19
20/// Cellular automaton
21#[derive(Debug, Clone)]
22pub struct CellularAutomaton {
23    /// Automaton name
24    name: String,
25    /// Type of automaton
26    cell_type: CellularType,
27    /// Rule (for 1D: 0-255)
28    rule: u8,
29    /// Size (number of cells for 1D, total cells for 2D)
30    size: usize,
31    /// Width (for grid layout)
32    width: usize,
33    /// Height (for grid layout)
34    height: usize,
35    /// Method for converting generation to output signal
36    output_mode: OutputMode,
37    /// Output value range
38    range: Range,
39    /// Random seed for initialization
40    rng_state: u64,
41}
42
43/// Generation-to-signal conversion mode
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum OutputMode {
46    /// Use the center cell
47    Center,
48    /// Use the sum of all cells (normalized)
49    Sum,
50    /// Use density of active cells
51    Density,
52    /// Use a specific index
53    Index(usize),
54}
55
56impl CellularAutomaton {
57    /// Create a new 1D cellular automaton
58    pub fn one_dimensional(name: &str, rule: u8, size: usize) -> Self {
59        Self {
60            name: name.to_string(),
61            cell_type: CellularType::OneDimensional,
62            rule,
63            size,
64            width: size,
65            height: 1,
66            output_mode: OutputMode::Center,
67            range: Range::unipolar(),
68            rng_state: 123456789,
69        }
70    }
71
72    /// Create a new Game of Life
73    pub fn game_of_life(name: &str, width: usize, height: usize) -> Self {
74        Self {
75            name: name.to_string(),
76            cell_type: CellularType::TwoDimensional,
77            rule: 0,
78            size: width * height,
79            width,
80            height,
81            output_mode: OutputMode::Density,
82            range: Range::unipolar(),
83            rng_state: 123456789,
84        }
85    }
86
87    /// Set the output mode
88    pub fn with_output_mode(mut self, mode: OutputMode) -> Self {
89        self.output_mode = mode;
90        self
91    }
92
93    /// Set the range
94    pub fn with_range(mut self, range: Range) -> Self {
95        self.range = range;
96        self
97    }
98
99    /// Initialize random state
100    fn random_initial(&self, rng: &mut u64) -> Vec<u8> {
101        let mut gen = Vec::with_capacity(self.size);
102        for _ in 0..self.size {
103            gen.push(if self.random_bit(rng) { 1 } else { 0 });
104        }
105        gen
106    }
107
108    /// Random bit
109    fn random_bit(&self, rng: &mut u64) -> bool {
110        let mut x = *rng;
111        x ^= x << 13;
112        x ^= x >> 7;
113        x ^= x << 17;
114        *rng = x;
115        (x & 1) == 1
116    }
117
118    /// Apply Wolfram rule (1D)
119    fn apply_rule_1d(&self, generation: &[u8]) -> Vec<u8> {
120        let mut next = vec![0; generation.len()];
121
122        for i in 0..generation.len() {
123            let left = if i > 0 {
124                generation[i - 1]
125            } else {
126                generation[generation.len() - 1]
127            };
128            let center = generation[i];
129            let right = if i < generation.len() - 1 {
130                generation[i + 1]
131            } else {
132                generation[0]
133            };
134
135            let pattern = (left << 2) | (center << 1) | right;
136            let bit = (self.rule >> pattern) & 1;
137            next[i] = bit;
138        }
139
140        next
141    }
142
143    /// Apply Game of Life rule (2D)
144    fn apply_rule_gol(&self, generation: &[u8]) -> Vec<u8> {
145        let mut next = vec![0; generation.len()];
146
147        for y in 0..self.height {
148            for x in 0..self.width {
149                let idx = y * self.width + x;
150                let cell = generation[idx];
151
152                // Count live neighbors (8 directions)
153                let mut neighbors = 0;
154                for dy in -1..=1 {
155                    for dx in -1..=1 {
156                        if dx == 0 && dy == 0 {
157                            continue;
158                        }
159
160                        let nx = (x as i32 + dx + self.width as i32) % self.width as i32;
161                        let ny = (y as i32 + dy + self.height as i32) % self.height as i32;
162                        let nidx = (ny * self.width as i32 + nx) as usize;
163
164                        if generation[nidx] == 1 {
165                            neighbors += 1;
166                        }
167                    }
168                }
169
170                // Game of Life rules
171                next[idx] = match (cell, neighbors) {
172                    (1, 2) | (1, 3) => 1, // Survival
173                    (0, 3) => 1,          // Birth
174                    _ => 0,               // Death
175                };
176            }
177        }
178
179        next
180    }
181
182    /// Compute the output value
183    fn compute_output(&self, generation: &[u8]) -> f64 {
184        match self.output_mode {
185            OutputMode::Center => {
186                let idx = self.size / 2;
187                generation[idx] as f64
188            }
189
190            OutputMode::Sum => {
191                let sum: usize = generation.iter().map(|&c| c as usize).sum();
192                sum as f64 / self.size as f64
193            }
194
195            OutputMode::Density => {
196                let sum: usize = generation.iter().map(|&c| c as usize).sum();
197                sum as f64 / self.size as f64
198            }
199
200            OutputMode::Index(idx) => {
201                if idx < generation.len() {
202                    generation[idx] as f64
203                } else {
204                    0.0
205                }
206            }
207        }
208    }
209}
210
211impl Automaton for CellularAutomaton {
212    type Internal = (Vec<u8>, usize);
213    type Action = NoAction;
214
215    fn step(
216        &self,
217        internal: &mut Self::Internal,
218        _current: &ParamValue,
219        _time: Time,
220        _action: &Self::Action,
221    ) -> ParamValue {
222        let (cells, generation) = internal;
223
224        *cells = match self.cell_type {
225            CellularType::OneDimensional => self.apply_rule_1d(cells),
226            CellularType::TwoDimensional => self.apply_rule_gol(cells),
227            CellularType::Cyclic => cells.clone(),
228        };
229        *generation += 1;
230
231        let raw = self.compute_output(cells);
232        let value = self.range.clamp(raw);
233        ParamValue::Float(value as f32)
234    }
235
236    fn initial_internal(&self) -> Self::Internal {
237        let mut rng = self.rng_state;
238        let generation = self.random_initial(&mut rng);
239        (generation, 0)
240    }
241
242    fn name(&self) -> &str {
243        &self.name
244    }
245}
246
247/// Preset rules for 1D cellular automata
248pub mod rules {
249    /// Rule 30: chaotic behavior
250    pub const RULE_30: u8 = 30;
251    /// Rule 90: fractal (Sierpinski triangle)
252    pub const RULE_90: u8 = 90;
253    /// Rule 110: universal (Turing-complete)
254    pub const RULE_110: u8 = 110;
255    /// Rule 184: traffic flow
256    pub const RULE_184: u8 = 184;
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_rule_30() {
265        let ca = CellularAutomaton::one_dimensional("Rule 30", rules::RULE_30, 31);
266        let mut internal = ca.initial_internal();
267        let current = ParamValue::Float(0.0);
268
269        let value = ca.step(&mut internal, &current, 0.0, &NoAction);
270        assert!(value.as_f32().is_some());
271    }
272}