baseq.rs
8.8 kB · rust · 273 lines
1use super::factory::residue_corners;2use super::universe::{permutations, Code};3use mrlycore::errors::{value_error, Result};4use mrlynum::classics::factorial;5use std::collections::{BTreeSet, HashMap};67/// The most cells a code walk visits, so that the walk stays within `2^20` codes.8pub const WALK_LIMIT: usize = 20;910/// Returns the distinct rotation and reflection maps of a base-q axis.11pub fn axis_maps(base: usize) -> Vec<Vec<usize>> {12 let mut out: Vec<Vec<usize>> = Vec::new();13 for b in 0..base {14 let rot: Vec<usize> = (0..base).map(|r| (r + b) % base).collect();15 let ref_: Vec<usize> = (0..base).map(|r| (base + b - r) % base).collect();16 if !out.contains(&rot) {17 out.push(rot);18 }19 if !out.contains(&ref_) {20 out.push(ref_);21 }22 }23 out24}2526/// Returns the symmetry group order counted from the enumerated axis maps.27pub fn group_order(base: usize, dimension: usize) -> u128 {28 (axis_maps(base).len() as u128).pow(dimension as u32) * factorial(dimension)29}3031/// Returns the closed-form group order the axis-map count must match.32pub fn predicted_group_order(base: usize, dimension: usize) -> u128 {33 let per_axis = if base == 2 { 2u128 } else { 2 * base as u128 };34 per_axis.pow(dimension as u32) * factorial(dimension)35}3637fn choices(axis: &[Vec<usize>], dimension: usize) -> Vec<Vec<usize>> {38 let mut out = vec![vec![]];39 for _ in 0..dimension {40 let mut next = Vec::new();41 for prefix in &out {42 for (i, _) in axis.iter().enumerate() {43 let mut item = prefix.clone();44 item.push(i);45 next.push(item);46 }47 }48 out = next;49 }50 out51}5253fn cycles(54 perm: &[usize],55 choice: &[usize],56 axis: &[Vec<usize>],57 cells: &[Vec<u8>],58 index: &HashMap<Vec<u8>, usize>,59) -> u32 {60 let apply = |corner: &[u8]| -> Vec<u8> {61 (0..corner.len())62 .map(|i| axis[choice[i]][corner[perm[i]] as usize] as u8)63 .collect()64 };65 let mut seen = vec![false; cells.len()];66 let mut count = 0;67 for start in 0..cells.len() {68 if seen[start] {69 continue;70 }71 count += 1;72 let mut j = start;73 while !seen[j] {74 seen[j] = true;75 j = index[&apply(&cells[j])];76 }77 }78 count79}8081/// Counts base-q designs distinct under symmetry, or an error when the Burnside average breaks.82pub fn distinct_designs(base: usize, dimension: usize) -> Result<u128> {83 if base < 1 {84 return value_error("base must be at least 1.");85 }86 if dimension < 1 {87 return value_error("dimension must be at least 1.");88 }89 let cells = residue_corners(dimension, base);90 let index: HashMap<Vec<u8>, usize> = cells91 .iter()92 .enumerate()93 .map(|(i, c)| (c.clone(), i))94 .collect();95 let axis = axis_maps(base);96 let mut order: u128 = 0;97 let mut total: u128 = 0;98 for perm in permutations(dimension) {99 for choice in choices(&axis, dimension) {100 order += 1;101 total += 1u128 << cycles(&perm, &choice, &axis, &cells, &index);102 }103 }104 if !total.is_multiple_of(order) {105 return value_error("Burnside average is not an integer.");106 }107 Ok(total / order)108}109110/// Returns the symmetry group as cell maps, each sending the cell at index `i` to `element[i]`.111pub fn group(base: usize, dimension: usize) -> Vec<Vec<usize>> {112 let cells = residue_corners(dimension, base);113 let axis = axis_maps(base);114 let mut out = Vec::new();115 for perm in permutations(dimension) {116 for choice in choices(&axis, dimension) {117 out.push(118 cells119 .iter()120 .map(|cell| {121 (0..dimension).fold(0, |acc, i| {122 acc * base + axis[choice[i]][cell[perm[i]] as usize]123 })124 })125 .collect(),126 );127 }128 }129 out130}131132/// Carries a code through one group element.133pub fn carry(element: &[usize], code: Code) -> Code {134 element135 .iter()136 .enumerate()137 .filter(|(index, _)| code >> index & 1 == 1)138 .map(|(_, &image)| 1u128 << image)139 .sum()140}141142/// Returns every code a design reaches under the group.143pub fn orbit(group: &[Vec<usize>], code: Code) -> BTreeSet<Code> {144 group.iter().map(|element| carry(element, code)).collect()145}146147/// Returns the least code of the design's orbit.148pub fn canonical(group: &[Vec<usize>], code: Code) -> Code {149 orbit(group, code)150 .into_iter()151 .next()152 .expect("the group is not empty")153}154155/// Walks every code of a base and dimension and returns each orbit's least code with the orbit's size, or an error past the walk limit.156///157/// ```158/// assert_eq!(mrlymath::bang::baseq::representatives(3, 1).unwrap().len(), 4);159/// ```160pub fn representatives(base: usize, dimension: usize) -> Result<Vec<(Code, usize)>> {161 let cells = base.pow(dimension as u32);162 if cells > WALK_LIMIT {163 return value_error(format!(164 "base {base} dimension {dimension} has {cells} cells, past the walk limit of {WALK_LIMIT}."165 ));166 }167 let group = group(base, dimension);168 let mut seen = vec![false; 1 << cells];169 let mut out = Vec::new();170 for code in 0..1u128 << cells {171 if seen[code as usize] {172 continue;173 }174 let orbit = orbit(&group, code);175 out.push((code, orbit.len()));176 for member in orbit {177 seen[member as usize] = true;178 }179 }180 Ok(out)181}182183/// Returns the raw design count before symmetry, two to the number of cells.184pub fn total_designs(base: usize, dimension: usize) -> u128 {185 let cells = base.pow(dimension as u32);186 assert!(cells < 128, "too many cells for a u128 count");187 1 << cells188}189190/// Returns the distinct-design counts for dimensions 1 through max_dimension.191pub fn sequence(base: usize, max_dimension: usize) -> Result<Vec<u128>> {192 (1..=max_dimension)193 .map(|d| distinct_designs(base, d))194 .collect()195}196197/// Returns the distinct one-dimensional design counts for bases 1 through max_base.198pub fn bracelets(max_base: usize) -> Result<Vec<u128>> {199 (1..=max_base).map(|q| distinct_designs(q, 1)).collect()200}201202/// Returns the filled-cell count of a binary design at a side number, folded from its filled corners.203pub fn fill_from_corners(filled: &[Vec<u8>], number: usize, dimension: usize) -> u128 {204 let even = number.div_ceil(2) as u128;205 let odd = (number / 2) as u128;206 filled207 .iter()208 .map(|corner| {209 let popcount = corner.iter().filter(|&&b| b != 0).count();210 even.pow((dimension - popcount) as u32) * odd.pow(popcount as u32)211 })212 .sum()213}214215/// Returns the collapsed fill count at an even side number, or an error at odd.216pub fn even_fill_is_balanced(number: usize, dimension: usize, popcount: u128) -> Result<u128> {217 if !number.is_multiple_of(2) {218 return value_error("the duality collapse holds only at even number.");219 }220 Ok(((number / 2) as u128).pow(dimension as u32) * popcount)221}222223#[cfg(test)]224mod tests {225 use super::*;226 #[test]227 fn group_orders_match_prediction() {228 for base in 2..=5 {229 for dimension in 1..=3 {230 assert_eq!(231 group_order(base, dimension),232 predicted_group_order(base, dimension)233 );234 }235 }236 }237 #[test]238 fn base2_matches_bang() {239 use super::super::universe::bang;240 for d in 1..=3 {241 assert_eq!(distinct_designs(2, d).unwrap(), bang(d).distinct() as u128);242 }243 }244 #[test]245 fn the_walk_agrees_with_burnside_and_the_base_two_universe() {246 use super::super::catalog::universe_codes;247 for (base, dimension) in [(3usize, 1usize), (3, 2), (4, 1), (4, 2), (5, 1)] {248 let walk = representatives(base, dimension).unwrap();249 assert_eq!(250 walk.len() as u128,251 distinct_designs(base, dimension).unwrap(),252 "q={base} d={dimension}"253 );254 let total: usize = walk.iter().map(|&(_, size)| size).sum();255 assert_eq!(total, 1 << base.pow(dimension as u32));256 }257 for dimension in 1..=4 {258 let codes: Vec<Code> = representatives(2, dimension)259 .unwrap()260 .into_iter()261 .map(|(code, _)| code)262 .collect();263 assert_eq!(codes, universe_codes(dimension));264 }265 assert_eq!(representatives(3, 2).unwrap().len(), 26);266 assert_eq!(representatives(4, 2).unwrap().len(), 805);267 assert!(representatives(3, 3).is_err());268 }269 #[test]270 fn bracelet_sequence_is_a000029() {271 assert_eq!(bracelets(8).unwrap(), vec![2, 3, 4, 6, 8, 13, 18, 30]);272 }273}