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