morse.rs

11.6 kB · rust · 373 lines

1use mrlycore::errors::{value_error, Result};23// THE WORD45/// Returns the Thue-Morse letter at the place, the parity of its binary digit sum.6///7/// ```8/// let word: Vec<u8> = (0..8).map(mrlynum::morse::letter).collect();9/// assert_eq!(word, vec![0, 1, 1, 0, 1, 0, 0, 1]);10/// ```11pub fn letter(place: u64) -> u8 {12    (place.count_ones() % 2) as u813}1415/// Builds the first letters of the Thue-Morse word by the digit rule.16pub fn digits(length: usize) -> Vec<u8> {17    (0..length as u64).map(letter).collect()18}1920/// Builds the first letters of the Thue-Morse word by the substitution `0 -> 01`, `1 -> 10`.21///22/// The seed is the single letter 0, and the rounds double the length until it covers the ask.23///24/// ```25/// assert_eq!(mrlynum::morse::substitution(8), mrlynum::morse::digits(8));26/// ```27pub fn substitution(length: usize) -> Vec<u8> {28    let mut word = vec![0u8];29    while word.len() < length {30        word = word.iter().flat_map(|&bit| [bit, 1 - bit]).collect();31    }32    word.truncate(length);33    word34}3536/// Returns the substitution stage after the rounds, a word of length two to the rounds.37pub fn stage(rounds: usize) -> Vec<u8> {38    let mut word = vec![0u8];39    for _ in 0..rounds {40        word = word.iter().flat_map(|&bit| [bit, 1 - bit]).collect();41    }42    word43}4445// RUNS4647/// Returns the lengths of the maximal blocks of one repeated letter, in order.48///49/// ```50/// assert_eq!(mrlynum::morse::runs(&[0, 1, 1, 0]), vec![1, 2, 1]);51/// ```52pub fn runs(word: &[u8]) -> Vec<usize> {53    let mut out: Vec<usize> = Vec::new();54    for (place, &bit) in word.iter().enumerate() {55        if place > 0 && bit == word[place - 1] {56            *out.last_mut().unwrap() += 1;57        } else {58            out.push(1);59        }60    }61    out62}6364/// Returns the run-boundary word, one wherever a letter differs from the next.65pub fn boundary(word: &[u8]) -> Vec<u8> {66    word.windows(2).map(|pair| pair[0] ^ pair[1]).collect()67}6869/// Builds the period-doubling word by the substitution `1 -> 10`, `0 -> 11`, from the seed 1.70///71/// ```72/// let word = mrlynum::morse::doubling(8);73/// assert_eq!(word, vec![1, 0, 1, 1, 1, 0, 1, 0]);74/// assert_eq!(word, mrlynum::morse::boundary(&mrlynum::morse::digits(9)));75/// ```76pub fn doubling(length: usize) -> Vec<u8> {77    let mut word = vec![1u8];78    while word.len() < length {79        word = word80            .iter()81            .flat_map(|&bit| if bit == 1 { [1, 0] } else { [1, 1] })82            .collect();83    }84    word.truncate(length);85    word86}8788// LIFTS8990/// The four ways the word lifts from a line to the plane, one sign at every site.91#[derive(Clone, Copy, Debug, PartialEq, Eq)]92pub enum Lift {93    /// `t(i) xor t(j)`, the sign grid of the two-by-two tile `[[+1, -1], [-1, +1]]`.94    Parity,95    /// `t(i and j)`, the Walsh-Hadamard pattern.96    And,97    /// `t(i xor j)`, which the parity of a xor forces equal to the first lift.98    Xor,99    /// `t(i + j)`, the one that carries and so does not fold.100    Sum,101}102103/// Lists the lifts in the order the gallery draws them.104pub const LIFTS: [Lift; 4] = [Lift::Parity, Lift::And, Lift::Xor, Lift::Sum];105106impl Lift {107    /// Parses a lift's display name, or errs on an unknown name.108    pub fn parse(name: &str) -> Result<Lift> {109        match name {110            "parity" => Ok(Lift::Parity),111            "and" => Ok(Lift::And),112            "xor" => Ok(Lift::Xor),113            "sum" => Ok(Lift::Sum),114            other => value_error(format!("unknown lift {other:?}.")),115        }116    }117    /// Returns the lift's display name.118    pub fn name(self) -> &'static str {119        match self {120            Lift::Parity => "parity",121            Lift::And => "and",122            Lift::Xor => "xor",123            Lift::Sum => "sum",124        }125    }126    /// Returns the lift's formula, written the way the page prints it.127    pub fn formula(self) -> &'static str {128        match self {129            Lift::Parity => "t(i) xor t(j)",130            Lift::And => "t(i and j)",131            Lift::Xor => "t(i xor j)",132            Lift::Sum => "t(i + j)",133        }134    }135    /// Returns the sign at a site, zero for plus one and one for minus one.136    pub fn at(self, i: u64, j: u64) -> u8 {137        match self {138            Lift::Parity => letter(i) ^ letter(j),139            Lift::And => letter(i & j),140            Lift::Xor => letter(i ^ j),141            Lift::Sum => letter(i + j),142        }143    }144}145146/// Builds a lift as a row-major sign grid of the side, zero for plus one and one for minus one.147pub fn lift(kind: Lift, side: usize) -> Vec<u8> {148    let mut out = vec![0u8; side * side];149    for i in 0..side {150        for j in 0..side {151            out[i * side + j] = kind.at(i as u64, j as u64);152        }153    }154    out155}156157// KRONECKER158159/// Folds a tile of the side into its Kronecker power at the level, one bit per site.160///161/// The bits ride the exclusive or, so the power is the digit rule of the tile: a site takes the162/// exclusive or of the tile's bits at its base-side digit pairs.163pub fn power(tile: &[u8], number: usize, level: usize) -> Result<Vec<u8>> {164    if tile.len() != number * number {165        return value_error(format!(166            "a side-{number} tile wants {} bits.",167            number * number168        ));169    }170    let side = match number.checked_pow(level as u32) {171        Some(side) => side,172        None => return value_error("that level passes what a machine holds."),173    };174    let mut out = vec![0u8; side * side];175    for r in 0..side {176        for c in 0..side {177            let (mut bit, mut row, mut col) = (0u8, r, c);178            for _ in 0..level {179                bit ^= tile[(row % number) * number + col % number];180                row /= number;181                col /= number;182            }183            out[r * side + c] = bit;184        }185    }186    Ok(out)187}188189/// The verdict on whether a grid is the Kronecker power of its own corner tile.190#[derive(Clone, Debug)]191pub struct Fold {192    /// The corner tile the test folds, row major.193    pub tile: Vec<u8>,194    /// The side of the tile.195    pub number: usize,196    /// The count of tile factors the side asks for.197    pub level: usize,198    /// Whether the grid is that tile's Kronecker power.199    pub folds: bool,200    /// The count of sites where the grid and the power differ.201    pub faults: usize,202    /// The first differing site in row-major order, when there is one.203    pub first: Option<(usize, usize)>,204}205206/// Tests a grid against the Kronecker power of its own corner tile.207///208/// The corner tile is the only candidate worth testing. If the grid is `T` folded `L` times then209/// its corner block is `T` with every bit flipped by `(L - 1) t00`, and folding that block `L`210/// times flips the grid by `L (L - 1) t00`, which is even. So a grid folds if and only if it211/// folds from its corner block, and no search over tiles is needed.212pub fn fold(grid: &[u8], side: usize, number: usize) -> Result<Fold> {213    if grid.len() != side * side {214        return value_error(format!("a side-{side} grid wants {} bits.", side * side));215    }216    if number < 2 {217        return value_error("a tile side is two or more.");218    }219    let mut level = 0usize;220    let mut reach = 1usize;221    while reach < side {222        reach *= number;223        level += 1;224    }225    if reach != side {226        return value_error(format!("side {side} is no power of {number}."));227    }228    let mut tile = vec![0u8; number * number];229    for r in 0..number {230        for c in 0..number {231            tile[r * number + c] = grid[r * side + c];232        }233    }234    let folded = power(&tile, number, level)?;235    let mut faults = 0usize;236    let mut first = None;237    for (at, (&here, &there)) in grid.iter().zip(&folded).enumerate() {238        if here != there {239            faults += 1;240            first.get_or_insert((at / side, at % side));241        }242    }243    Ok(Fold {244        tile,245        number,246        level,247        folds: faults == 0,248        faults,249        first,250    })251}252253// THE FILTER254255/// Blows a grid up by the scale, every site becoming a scale-by-scale block.256pub fn upsample(grid: &[u8], side: usize, scale: usize) -> Vec<u8> {257    let wide = side * scale;258    let mut out = vec![0u8; wide * wide];259    for r in 0..wide {260        for c in 0..wide {261            out[r * wide + c] = grid[(r / scale) * side + c / scale];262        }263    }264    out265}266267/// Repeats a tile until it fills a grid of the side.268pub fn repeat(tile: &[u8], number: usize, side: usize) -> Vec<u8> {269    let mut out = vec![0u8; side * side];270    for r in 0..side {271        for c in 0..side {272            out[r * side + c] = tile[(r % number) * number + c % number];273        }274    }275    out276}277278/// Exclusive-ors two grids of the same length, site by site.279pub fn difference(a: &[u8], b: &[u8]) -> Vec<u8> {280    a.iter().zip(b).map(|(&x, &y)| x ^ y).collect()281}282283/// Counts the sites where two grids of the same length differ.284pub fn faults(a: &[u8], b: &[u8]) -> usize {285    a.iter().zip(b).filter(|(x, y)| x != y).count()286}287288#[cfg(test)]289mod tests {290    use super::*;291292    #[test]293    fn the_two_constructions_of_the_word_agree() {294        assert_eq!(digits(4096), substitution(4096));295        assert_eq!(stage(6), digits(64));296        assert_eq!(297            digits(16),298            vec![0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0]299        );300    }301302    #[test]303    fn the_word_is_cube_free_at_the_letter() {304        let word = digits(1 << 16);305        assert_eq!(runs(&word).into_iter().max(), Some(2));306        let counts = runs(&word);307        assert_eq!(counts.iter().filter(|&&run| run == 1).count(), 21846);308        assert_eq!(counts.iter().filter(|&&run| run == 2).count(), 21845);309        assert_eq!(word.iter().map(|&bit| bit as usize).sum::<usize>(), 1 << 15);310    }311312    #[test]313    fn the_boundary_word_is_the_period_doubling_word() {314        let word = digits(65537);315        assert_eq!(boundary(&word), doubling(65536));316    }317318    #[test]319    fn three_of_the_four_lifts_fold_and_the_sum_does_not() {320        let side = 64;321        for kind in [Lift::Parity, Lift::And, Lift::Xor] {322            let read = fold(&lift(kind, side), side, 2).unwrap();323            assert!(read.folds, "{}", kind.formula());324            assert_eq!(read.level, 6);325        }326        assert_eq!(327            fold(&lift(Lift::Parity, side), side, 2).unwrap().tile,328            vec![0, 1, 1, 0]329        );330        assert_eq!(331            fold(&lift(Lift::And, side), side, 2).unwrap().tile,332            vec![0, 0, 0, 1]333        );334        let sum = fold(&lift(Lift::Sum, side), side, 2).unwrap();335        assert!(!sum.folds);336        assert_eq!(sum.first, Some((1, 3)));337        assert_eq!(sum.faults, 1376);338    }339340    #[test]341    fn the_xor_lift_is_the_parity_lift() {342        for side in [2, 4, 8, 16, 32] {343            assert_eq!(lift(Lift::Xor, side), lift(Lift::Parity, side));344        }345        assert_eq!(faults(&lift(Lift::Sum, 32), &lift(Lift::Parity, 32)), 448);346    }347348    #[test]349    fn the_sum_lift_is_flat_on_every_antidiagonal() {350        let side = 32;351        let grid = lift(Lift::Sum, side);352        for r in 0..side {353            for c in 0..side {354                assert_eq!(grid[r * side + c], grid[c * side + r]);355                if r > 0 && c + 1 < side {356                    assert_eq!(grid[r * side + c], grid[(r - 1) * side + c + 1]);357                }358            }359        }360    }361362    #[test]363    fn the_difference_of_two_sign_levels_is_the_tile_repeated() {364        let tile = vec![0, 1, 1, 0];365        for level in 1..6 {366            let coarse = power(&tile, 2, level).unwrap();367            let fine = power(&tile, 2, level + 1).unwrap();368            let side = 1 << level;369            let grown = upsample(&coarse, side, 2);370            assert_eq!(difference(&grown, &fine), repeat(&tile, 2, side * 2));371        }372    }373}