presets.rs

9.9 kB · rust · 330 lines

1use mrlymath::bang::universe::{orbit, Code};2use mrlymath::life::{Boundary, Counts, Sequence};34/// The Moore neighbourhood's code at side 3 in the plane.5pub const MOORE: Code = 7;6/// The Menger tile's code at side 3 in space.7pub const MENGER: Code = 23;8/// The 26-cell neighbourhood's code at side 3 in space.9pub const MOORE_3D: Code = 127;1011/// One seed design: a universe code drawn at a side and a Kronecker level.12#[derive(Clone, Copy, Debug, PartialEq, Eq)]13pub struct Seed {14    /// The universe code.15    pub code: Code,16    /// The odd side of the level-1 tile.17    pub side: usize,18    /// The Kronecker level.19    pub level: usize,20}2122/// One neighbourhood mask: a popped design, or the seed board copied and optionally inverted.23#[derive(Clone, Copy, Debug, PartialEq, Eq)]24pub enum Mask {25    /// The design a code names at a side and level, centre popped.26    Design {27        /// The universe code.28        code: Code,29        /// The odd side.30        side: usize,31        /// The Kronecker level.32        level: usize,33    },34    /// The tessellated seed board itself, centre popped.35    Copy {36        /// Whether the board is inverted before popping.37        inverted: bool,38    },39}4041impl Mask {42    /// Returns the mask's printable name.43    pub fn name(&self) -> String {44        match self {45            Mask::Design { code, side, level } if *code == MOORE && *side == 3 && *level == 1 => {46                "moore".to_string()47            }48            Mask::Design { code, side, level } => format!("d{code}s{side}l{level}"),49            Mask::Copy { inverted: false } => "copy".to_string(),50            Mask::Copy { inverted: true } => "copyinv".to_string(),51        }52    }53}5455/// One rule: a sequence drawn into both sides with its zero and one rolls, Life, or the whole outer-totalistic space.56#[derive(Clone, Copy, Debug, PartialEq, Eq)]57pub enum Rule {58    /// Birth and survive both drawn from one sequence.59    Drawn {60        /// The sequence behind both sides.61        seq: Sequence,62        /// Whether zero stays in the counts.63        zeros: bool,64        /// Whether one stays in the counts.65        ones: bool,66    },67    /// Conway's B3/S23.68    Life,69    /// Every outer-totalistic rule of the mask, declared for the largest preset only.70    Every,71}7273impl Rule {74    /// Returns the rule's printable name.75    pub fn name(&self) -> String {76        match self {77            Rule::Drawn { seq, zeros, ones } => {78                let mut name = seq.name();79                if *zeros {80                    name.push_str("+0");81                }82                if *ones {83                    name.push_str("+1");84                }85                name86            }87            Rule::Life => "b3s23".to_string(),88            Rule::Every => "every".to_string(),89        }90    }91    /// Returns the rule's family: number, design, life or every.92    pub fn family(&self) -> &'static str {93        match self {94            Rule::Drawn { seq, .. } if Sequence::numbers().contains(seq) => "number",95            Rule::Drawn { .. } => "design",96            Rule::Life => "life",97            Rule::Every => "every",98        }99    }100    /// Returns the counts that create a cell.101    pub fn birth(&self) -> Counts {102        match self {103            Rule::Drawn { seq, zeros, ones } => Counts::drawn(*seq, *zeros, *ones),104            Rule::Life => Counts::List(vec![3]),105            Rule::Every => Counts::List(Vec::new()),106        }107    }108    /// Returns the counts that keep a cell.109    pub fn survive(&self) -> Counts {110        match self {111            Rule::Drawn { seq, zeros, ones } => Counts::drawn(*seq, *zeros, *ones),112            Rule::Life => Counts::List(vec![2, 3]),113            Rule::Every => Counts::List(Vec::new()),114        }115    }116}117118/// One census preset: every axis the loop sweeps and the cut it stops at.119#[derive(Clone, Debug, PartialEq, Eq)]120pub struct Preset {121    /// The preset's name.122    pub name: &'static str,123    /// The dimension the seeds live in.124    pub dimension: usize,125    /// The seed designs.126    pub seeds: Vec<Seed>,127    /// The odd tessellation factors.128    pub tessellations: Vec<usize>,129    /// The masks.130    pub masks: Vec<Mask>,131    /// The rules.132    pub rules: Vec<Rule>,133    /// The boundaries.134    pub boundaries: Vec<Boundary>,135    /// The canvas side every board is padded to.136    pub canvas: usize,137    /// The generation cap.138    pub generations: usize,139}140141impl Preset {142    /// Returns the nominal run count before any dedupe.143    pub fn runs(&self) -> usize {144        let rules: usize = self145            .rules146            .iter()147            .map(|rule| if *rule == Rule::Every { 1 << 18 } else { 1 })148            .sum();149        self.seeds.len()150            * self.tessellations.len()151            * self.masks.len()152            * rules153            * self.boundaries.len()154    }155    /// Returns the preset as printable lines.156    pub fn describe(&self) -> Vec<String> {157        let seeds: Vec<String> = self158            .seeds159            .iter()160            .map(|s| format!("{}s{}l{}", s.code, s.side, s.level))161            .collect();162        let masks: Vec<String> = self.masks.iter().map(Mask::name).collect();163        let rules: Vec<String> = self.rules.iter().map(Rule::name).collect();164        let bounds: Vec<String> = self165            .boundaries166            .iter()167            .map(|b| format!("{b:?}").to_lowercase())168            .collect();169        vec![170            format!("preset {} dimension {}", self.name, self.dimension),171            format!("seeds {} : {}", seeds.len(), seeds.join(" ")),172            format!("tessellations {:?}", self.tessellations),173            format!("masks {} : {}", masks.len(), masks.join(" ")),174            format!("rules {} : {}", rules.len(), rules.join(" ")),175            format!("boundaries {}", bounds.join(" ")),176            format!("canvas {} generations {}", self.canvas, self.generations),177            format!("nominal runs {}", self.runs()),178        ]179    }180}181182/// Returns the smallest code of every non-empty orbit of a dimension's designs.183pub fn classes(dimension: usize) -> Vec<Code> {184    let total: Code = 1 << (1 << dimension);185    (1..total)186        .filter(|&code| orbit(code, dimension).iter().next() == Some(&code))187        .collect()188}189190fn drawn(zeros: bool, ones: bool) -> Vec<Rule> {191    Sequence::all()192        .into_iter()193        .map(|seq| Rule::Drawn { seq, zeros, ones })194        .collect()195}196197/// The mini preset: side-3 classes at level 1, tessellation 1 and 3, side-3 masks and Copy, rolls off, Wrap, canvas 27, 64 generations.198pub fn mini() -> Preset {199    let seeds = classes(2)200        .into_iter()201        .map(|code| Seed {202            code,203            side: 3,204            level: 1,205        })206        .collect();207    let mut masks = vec![Mask::Design {208        code: MOORE,209        side: 3,210        level: 1,211    }];212    masks.extend(213        classes(2)214            .into_iter()215            .filter(|&code| code != MOORE)216            .map(|code| Mask::Design {217                code,218                side: 3,219                level: 1,220            }),221    );222    masks.push(Mask::Copy { inverted: false });223    masks.push(Mask::Copy { inverted: true });224    let mut rules = drawn(false, false);225    rules.push(Rule::Life);226    Preset {227        name: "mini",228        dimension: 2,229        seeds,230        tessellations: vec![1, 3],231        masks,232        rules,233        boundaries: vec![Boundary::Wrap],234        canvas: 27,235        generations: 64,236    }237}238239/// The medi preset: sides 3 and 5 at levels 1 and 2, tessellation to 5, masks to side 9 and Copy, all rolls, both boundaries, canvas 243, 256 generations.240pub fn medi() -> Preset {241    let mut seeds = Vec::new();242    for side in [3, 5] {243        for level in [1, 2] {244            seeds.extend(245                classes(2)246                    .into_iter()247                    .map(|code| Seed { code, side, level }),248            );249        }250    }251    let mut masks = Vec::new();252    for side in [3, 5, 7, 9] {253        masks.extend(classes(2).into_iter().map(|code| Mask::Design {254            code,255            side,256            level: 1,257        }));258    }259    masks.push(Mask::Copy { inverted: false });260    masks.push(Mask::Copy { inverted: true });261    let mut rules = Vec::new();262    for zeros in [false, true] {263        for ones in [false, true] {264            rules.extend(drawn(zeros, ones));265        }266    }267    rules.push(Rule::Life);268    Preset {269        name: "medi",270        dimension: 2,271        seeds,272        tessellations: vec![1, 3, 5],273        masks,274        rules,275        boundaries: vec![Boundary::Wrap, Boundary::Constant],276        canvas: 243,277        generations: 256,278    }279}280281/// The maxi preset: the side-3 classes in space, the Menger and Moore masks to level 3, tessellation to 11, every outer-totalistic rule, canvas 243, 1024 generations.282pub fn maxi() -> Preset {283    let seeds = classes(3)284        .into_iter()285        .map(|code| Seed {286            code,287            side: 3,288            level: 1,289        })290        .collect();291    let mut masks = Vec::new();292    for level in [1, 2, 3] {293        masks.push(Mask::Design {294            code: MENGER,295            side: 3,296            level,297        });298    }299    masks.push(Mask::Design {300        code: MOORE_3D,301        side: 3,302        level: 1,303    });304    Preset {305        name: "maxi",306        dimension: 3,307        seeds,308        tessellations: vec![1, 3, 5, 7, 9, 11],309        masks,310        rules: vec![Rule::Every],311        boundaries: vec![Boundary::Wrap, Boundary::Constant],312        canvas: 243,313        generations: 1024,314    }315}316317#[cfg(test)]318mod tests {319    use super::*;320    #[test]321    fn the_plane_has_five_filled_classes_and_the_mini_preset_a_thousand_runs() {322        assert_eq!(classes(2), vec![1, 3, 6, 7, 15]);323        assert_eq!(classes(3).len(), 21);324        let preset = mini();325        assert_eq!(preset.masks[0].name(), "moore");326        assert_eq!(preset.rules.len(), 24);327        assert_eq!(preset.runs(), 5 * 2 * 7 * 24);328        assert_eq!(maxi().rules[0].family(), "every");329    }330}