universe.rs
12.0 kB · rust · 398 lines
1use std::collections::BTreeSet;23/// The bitmask of filled corners that names a design.4pub type Code = u128;56/// Returns every permutation of 0..n in sorted order.7pub fn permutations(n: usize) -> Vec<Vec<usize>> {8 if n == 0 {9 return vec![vec![]];10 }11 let mut out = Vec::new();12 let mut items: Vec<usize> = (0..n).collect();13 heap(&mut items, n, &mut out);14 out.sort();15 out16}1718fn heap(items: &mut Vec<usize>, k: usize, out: &mut Vec<Vec<usize>>) {19 if k == 1 {20 out.push(items.clone());21 return;22 }23 for i in 0..k {24 heap(items, k - 1, out);25 if k.is_multiple_of(2) {26 items.swap(i, k - 1);27 } else {28 items.swap(0, k - 1);29 }30 }31}3233/// Returns the binary corners of a dimension in code order.34pub fn corners(dimension: usize) -> Vec<Vec<u8>> {35 (0..1usize << dimension)36 .map(|i| {37 (0..dimension)38 .map(|j| ((i >> (dimension - 1 - j)) & 1) as u8)39 .collect()40 })41 .collect()42}4344/// Returns the bit position a binary corner occupies in a code.45pub fn corner_index(corner: &[u8]) -> usize {46 corner.iter().fold(0, |acc, &b| (acc << 1) | b as usize)47}4849/// Returns the full symmetry group as axis permutations paired with flip patterns.50pub fn symmetries(dimension: usize) -> Vec<(Vec<usize>, Vec<u8>)> {51 let mut out = Vec::new();52 for perm in permutations(dimension) {53 for f in 0..1usize << dimension {54 let flips: Vec<u8> = (0..dimension)55 .map(|j| ((f >> (dimension - 1 - j)) & 1) as u8)56 .collect();57 out.push((perm.clone(), flips));58 }59 }60 out61}6263/// Applies a symmetry element to a corner.64pub fn apply(element: &(Vec<usize>, Vec<u8>), corner: &[u8]) -> Vec<u8> {65 let (perm, flips) = element;66 (0..corner.len())67 .map(|i| corner[perm[i]] ^ flips[i])68 .collect()69}7071/// Returns every code a design reaches under the full symmetry group.72pub fn orbit(code: Code, dimension: usize) -> BTreeSet<Code> {73 let cells = corners(dimension);74 let group = symmetries(dimension);75 let mut out = BTreeSet::new();76 for g in &group {77 let mut image: Code = 0;78 for (i, cell) in cells.iter().enumerate() {79 if (code >> i) & 1 == 1 {80 image |= 1 << corner_index(&apply(g, cell));81 }82 }83 out.insert(image);84 }85 out86}8788/// Returns the algebraic normal form coefficients of a code, one per corner.89pub fn anf(code: Code, dimension: usize) -> Vec<u8> {90 let cells = corners(dimension);91 let mut coeff: Vec<u8> = (0..cells.len()).map(|i| ((code >> i) & 1) as u8).collect();92 for axis in 0..dimension {93 for (i, cell) in cells.iter().enumerate() {94 if cell[axis] == 1 {95 let mut lower = cell.clone();96 lower[axis] = 0;97 coeff[i] ^= coeff[corner_index(&lower)];98 }99 }100 }101 coeff102}103104/// Returns the algebraic degree of a code, or -1 for the zero design.105pub fn degree(code: Code, dimension: usize) -> i32 {106 let cells = corners(dimension);107 let coeff = anf(code, dimension);108 cells109 .iter()110 .enumerate()111 .filter(|(i, _)| coeff[*i] == 1)112 .map(|(_, c)| c.iter().map(|&b| b as i32).sum())113 .max()114 .unwrap_or(-1)115}116117/// Returns whether no two filled corners of a code sit at Hamming distance one.118///119/// Such a design buries no face at any side and any level, so its surface is six per cell.120///121/// ```122/// assert!(mrlymath::bang::universe::total_exposure(129, 3));123/// assert!(!mrlymath::bang::universe::total_exposure(23, 3));124/// ```125pub fn total_exposure(code: Code, dimension: usize) -> bool {126 let cells = corners(dimension);127 for (i, cell) in cells.iter().enumerate() {128 if (code >> i) & 1 == 0 {129 continue;130 }131 for axis in 0..dimension {132 let mut neighbor = cell.clone();133 neighbor[axis] ^= 1;134 if (code >> corner_index(&neighbor)) & 1 == 1 {135 return false;136 }137 }138 }139 true140}141142/// Returns whether a code fills the all-even corner, the rule that touches every grid corner at odd side.143///144/// ```145/// assert!(mrlymath::bang::universe::touches_every_corner(23, 3));146/// assert!(!mrlymath::bang::universe::touches_every_corner(232, 3));147/// ```148pub fn touches_every_corner(code: Code, dimension: usize) -> bool {149 let all_even: Vec<u8> = vec![0; dimension];150 (code >> corner_index(&all_even)) & 1 == 1151}152153/// Formats the algebraic normal form of a code as a sum of monomials.154pub fn anf_string(code: Code, dimension: usize) -> String {155 const NAMES: [char; 6] = ['x', 'y', 'z', 'w', 'v', 'u'];156 let cells = corners(dimension);157 let coeff = anf(code, dimension);158 let mut order: Vec<usize> = (0..cells.len()).collect();159 order.sort_by_key(|&i| {160 (161 cells[i].iter().map(|&b| b as usize).sum::<usize>(),162 cells[i].clone(),163 )164 });165 let mut terms = Vec::new();166 for i in order {167 if coeff[i] == 1 {168 let popcount: usize = cells[i].iter().map(|&b| b as usize).sum();169 if popcount == 0 {170 terms.push("1".to_string());171 } else {172 terms.push(173 (0..dimension)174 .filter(|&j| cells[i][j] == 1)175 .map(|j| NAMES[j])176 .collect(),177 );178 }179 }180 }181 if terms.is_empty() {182 "0".to_string()183 } else {184 terms.join("+")185 }186}187188/// A single design with its place in the orbit structure.189#[derive(Clone, Debug)]190pub struct Design {191 /// The design's code.192 pub i: Code,193 /// The design's dimension.194 pub dimension: usize,195 /// Whether this code is the smallest in its orbit.196 pub canonical: bool,197 /// The smallest code in the orbit.198 pub class_rep: Code,199 /// The number of codes in the orbit.200 pub orbit_size: usize,201}202203impl Design {204 /// Returns the design's name as a line of prose, `bang dim 2, code 7`.205 pub fn name(&self) -> String {206 crate::name::Named::to_mrly(&crate::name::Bang::new(self.i, self.dimension, 2))207 }208 /// Returns the design's filled corners in sorted order.209 pub fn rule(&self) -> Vec<Vec<u8>> {210 let cells = corners(self.dimension);211 let mut out: Vec<Vec<u8>> = cells212 .into_iter()213 .enumerate()214 .filter(|(i, _)| (self.i >> i) & 1 == 1)215 .map(|(_, c)| c)216 .collect();217 out.sort();218 out219 }220 /// Returns the design's algebraic degree, or -1 for the zero design.221 pub fn degree(&self) -> i32 {222 degree(self.i, self.dimension)223 }224 /// Returns the design's algebraic normal form as a string.225 pub fn anf(&self) -> String {226 anf_string(self.i, self.dimension)227 }228}229230/// The complete enumeration of one dimension's designs and orbits.231pub struct Universe {232 /// The universe's dimension.233 pub dimension: usize,234 /// The number of codes in the universe.235 pub total: usize,236 class_rep: Vec<Code>,237 orbit_size: Vec<usize>,238}239240impl Universe {241 /// Enumerates every orbit of a dimension from 1 to 4.242 pub fn new(dimension: usize) -> Self {243 assert!(244 (1..=4).contains(&dimension),245 "bang is enumerable only for dimensions 1-4"246 );247 let total = 1usize << (1usize << dimension);248 let mut class_rep = Vec::with_capacity(total);249 let mut orbit_size = Vec::with_capacity(total);250 for code in 0..total {251 let orb = orbit(code as Code, dimension);252 class_rep.push(*orb.iter().next().unwrap());253 orbit_size.push(orb.len());254 }255 Universe {256 dimension,257 total,258 class_rep,259 orbit_size,260 }261 }262 /// Returns the design at a code with its precomputed orbit facts.263 pub fn design(&self, code: Code) -> Design {264 let rep = self.class_rep[code as usize];265 Design {266 i: code,267 dimension: self.dimension,268 canonical: rep == code,269 class_rep: rep,270 orbit_size: self.orbit_size[code as usize],271 }272 }273 /// Returns every design in code order.274 pub fn all(&self) -> Vec<Design> {275 (0..self.total)276 .map(|code| self.design(code as Code))277 .collect()278 }279 /// Returns the designs whose codes lead their orbits.280 pub fn canonical(&self) -> Vec<Design> {281 self.all().into_iter().filter(|d| d.canonical).collect()282 }283 /// Returns the number of distinct orbits.284 pub fn distinct(&self) -> usize {285 let mut reps: Vec<Code> = self.class_rep.clone();286 reps.sort();287 reps.dedup();288 reps.len()289 }290}291292/// Builds the universe of a dimension.293///294/// ```295/// let u = mrlymath::bang::bang(2);296/// assert_eq!(u.total, 16);297/// assert_eq!(u.distinct(), 6);298/// ```299pub fn bang(dimension: usize) -> Universe {300 Universe::new(dimension)301}302303#[cfg(test)]304mod tests {305 use super::*;306 #[test]307 fn total_and_distinct_counts() {308 assert_eq!(bang(1).distinct(), 3);309 assert_eq!(bang(2).distinct(), 6);310 assert_eq!(bang(3).distinct(), 22);311 assert_eq!(bang(1).total, 4);312 assert_eq!(bang(2).total, 16);313 assert_eq!(bang(3).total, 256);314 }315 #[test]316 fn prefix_codes_canonical() {317 for d in 1..=3 {318 let u = bang(d);319 for k in 0..=(1usize << d) {320 let code = (1u128 << k) - 1;321 if (code as usize) < u.total {322 assert!(u.design(code).canonical);323 }324 }325 }326 }327 #[test]328 fn anti_closure_3d() {329 let u = bang(3);330 let full: Code = (1 << (1 << 3)) - 1;331 let reps: Vec<Code> = u.canonical().iter().map(|d| d.class_rep).collect();332 for d in u.canonical() {333 let anti = full ^ d.i;334 assert!(reps.contains(&u.design(anti).class_rep));335 }336 }337 #[test]338 fn orbit_sizes_partition() {339 for d in 2..=3usize {340 let u = bang(d);341 let order = (1usize << d) * (1..=d).product::<usize>();342 let total: usize = u.canonical().iter().map(|x| x.orbit_size).sum();343 assert_eq!(total, u.total);344 for x in u.canonical() {345 assert_eq!(order % x.orbit_size, 0);346 }347 }348 }349 #[test]350 fn degree_histogram_3d() {351 let u = bang(3);352 let mut hist = std::collections::HashMap::new();353 for d in u.canonical() {354 *hist.entry(d.degree()).or_insert(0) += 1;355 }356 let expected: std::collections::HashMap<i32, i32> =357 [(-1, 1), (0, 1), (1, 3), (2, 9), (3, 8)]358 .into_iter()359 .collect();360 assert_eq!(hist, expected);361 }362 #[test]363 fn total_exposure_names_the_independent_corner_sets() {364 let exposed: Vec<Code> = (0..256).filter(|&c| total_exposure(c, 3)).collect();365 assert_eq!(exposed.len(), 35);366 let classes: BTreeSet<Code> = exposed367 .iter()368 .map(|&c| *orbit(c, 3).iter().next().unwrap())369 .collect();370 assert_eq!(371 classes.into_iter().collect::<Vec<Code>>(),372 [0, 1, 6, 22, 24, 105]373 );374 assert!(total_exposure(129, 3));375 assert!(!total_exposure(23, 3));376 }377378 #[test]379 fn half_the_rules_hold_the_all_even_corner() {380 assert_eq!(381 (0..256).filter(|&c| touches_every_corner(c, 3)).count(),382 128383 );384 for code in [23u128, 3, 129] {385 assert!(touches_every_corner(code, 3), "code={code}");386 }387 assert!(!touches_every_corner(232, 3));388 }389390 #[test]391 fn names_and_anf() {392 let u = bang(2);393 assert_eq!(u.design(0).name(), "bang dim 2, code 0");394 assert_eq!(u.design(7).name(), "bang dim 2, code 7");395 assert_eq!(u.design(0).anf(), "0");396 assert_eq!(u.design(1).anf(), "1+y+x+xy");397 }398}