counting.rs

5.9 kB · rust · 202 lines

1use mrlycore::errors::{value_error, Result};2use mrlynum::classics::{factorial, gcd};3use mrlynum::factor::{divisors, mobius};4use std::collections::BTreeMap;56type Cycles = BTreeMap<usize, u128>;78fn pos_block_cycles(length: usize) -> Cycles {9    let mut out = Cycles::new();10    for period in divisors(length) {11        let strings: i128 = divisors(period)12            .iter()13            .map(|&d| i128::from(mobius(period / d)) * (1i128 << d))14            .sum();15        *out.entry(period).or_insert(0) += (strings / period as i128) as u128;16    }17    out18}1920fn neg_block_cycles(length: usize) -> Cycles {21    let states = 1usize << length;22    let step = |x: usize| -> usize {23        let mut new = vec![0usize; length];24        for (i, item) in new.iter_mut().enumerate() {25            *item = (x >> ((i + length - 1) % length)) & 1;26        }27        new[0] ^= 1;28        new.iter().enumerate().map(|(i, &b)| b << i).sum()29    };30    let mut seen = vec![false; states];31    let mut out = Cycles::new();32    for start in 0..states {33        if seen[start] {34            continue;35        }36        let mut run = 0;37        let mut j = start;38        while !seen[j] {39            seen[j] = true;40            j = step(j);41            run += 1;42        }43        *out.entry(run).or_insert(0) += 1;44    }45    out46}4748fn combine(c1: &Cycles, c2: &Cycles) -> Cycles {49    let mut out = Cycles::new();50    for (&l1, &n1) in c1 {51        for (&l2, &n2) in c2 {52            let g = gcd(l1 as u128, l2 as u128) as usize;53            *out.entry(l1 * l2 / g).or_insert(0) += n1 * n2 * g as u128;54        }55    }56    out57}5859fn class_cycles(pos: &[usize], neg: &[usize]) -> u32 {60    let blocks: Vec<Cycles> = pos61        .iter()62        .map(|&l| pos_block_cycles(l))63        .chain(neg.iter().map(|&l| neg_block_cycles(l)))64        .collect();65    if blocks.is_empty() {66        return 1;67    }68    let mut acc = blocks[0].clone();69    for b in &blocks[1..] {70        acc = combine(&acc, b);71    }72    acc.values().sum::<u128>() as u3273}7475fn partitions(n: usize, m: usize) -> Vec<Vec<usize>> {76    if n == 0 {77        return vec![vec![]];78    }79    let mut out = Vec::new();80    for k in (1..=n.min(m)).rev() {81        for rest in partitions(n - k, k) {82            let mut item = vec![k];83            item.extend(rest);84            out.push(item);85        }86    }87    out88}8990fn bipartitions(dimension: usize) -> Vec<(Vec<usize>, Vec<usize>)> {91    let mut out = Vec::new();92    for s in 0..=dimension {93        for pos in partitions(s, s.max(1)) {94            for neg in partitions(dimension - s, (dimension - s).max(1)) {95                out.push((pos.clone(), neg));96            }97        }98    }99    out100}101102fn class_size(pos: &[usize], neg: &[usize], dimension: usize) -> u128 {103    let mut centralizer: u128 = 1;104    for part in [pos, neg] {105        let mut mults = BTreeMap::new();106        for &length in part {107            *mults.entry(length).or_insert(0usize) += 1;108        }109        for (length, mult) in mults {110            centralizer *= factorial(mult) * (2 * length as u128).pow(mult as u32);111        }112    }113    ((1u128 << dimension) * factorial(dimension)) / centralizer114}115116/// Returns the raw design count of a dimension, two to the number of corners.117pub fn total_designs(dimension: usize) -> u128 {118    assert!(dimension <= 6, "total exceeds u128 above dimension 6");119    1 << (1 << dimension)120}121122/// Counts designs distinct under the full symmetry group class by class, or an error when the sums break.123pub fn distinct_designs(dimension: usize) -> Result<u128> {124    let order = (1u128 << dimension) * factorial(dimension);125    let mut total: u128 = 0;126    let mut checked: u128 = 0;127    for (pos, neg) in bipartitions(dimension) {128        let size = class_size(&pos, &neg, dimension);129        checked += size;130        total += size * (1u128 << class_cycles(&pos, &neg));131    }132    if checked != order {133        return value_error("class sizes do not sum to the group order.");134    }135    if !total.is_multiple_of(order) {136        return value_error("Burnside average is not an integer.");137    }138    Ok(total / order)139}140141/// Returns the distinct-design counts for dimensions 1 through max_dimension.142///143/// ```144/// assert_eq!(mrlymath::bang::counting::sequence(4).unwrap(), vec![3, 6, 22, 402]);145/// ```146pub fn sequence(max_dimension: usize) -> Result<Vec<u128>> {147    (1..=max_dimension).map(distinct_designs).collect()148}149150/// Counts the fill classes of a dimension, the popcount profiles a base-2 design can have: one more than the corners of each weight, multiplied over the weights, A129824 at the dimension.151pub fn classes(dimension: usize) -> u128 {152    let mut row = vec![1u128];153    for _ in 0..dimension {154        let mut next = vec![1u128; row.len() + 1];155        for k in 1..row.len() {156            next[k] = row[k - 1] + row[k];157        }158        row = next;159    }160    row.iter().map(|corners| corners + 1).product()161}162163/// Returns the fill-class counts for dimensions 1 through max_dimension.164///165/// ```166/// assert_eq!(mrlymath::bang::counting::class_sequence(4), vec![4, 12, 64, 700]);167/// ```168pub fn class_sequence(max_dimension: usize) -> Vec<u128> {169    (1..=max_dimension).map(classes).collect()170}171172#[cfg(test)]173mod tests {174    use super::*;175    #[test]176    fn burnside_matches_enumeration() {177        use super::super::universe::bang;178        for d in 1..=3 {179            assert_eq!(distinct_designs(d).unwrap(), bang(d).distinct() as u128);180        }181    }182    #[test]183    fn sequence_is_a000616() {184        assert_eq!(185            sequence(6).unwrap(),186            vec![3, 6, 22, 402, 1228158, 400507806843728]187        );188    }189    #[test]190    fn classes_are_a129824() {191        assert_eq!(classes(0), 2);192        assert_eq!(193            class_sequence(7),194            vec![4, 12, 64, 700, 17424, 1053696, 160579584]195        );196    }197    #[test]198    fn totals_doubly_exponential() {199        let totals: Vec<u128> = (1..=4).map(total_designs).collect();200        assert_eq!(totals, vec![4, 16, 256, 65536]);201    }202}