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}