arcs.rs

8.5 kB · rust · 222 lines

1use crate::core::error::{overflow_error, value_error, Result};2use crate::math::bang::factory::{code_to_corners, create};3use crate::math::bang::Code;4use serde::{Deserialize, Serialize};56// THE ARCS78/// One level of a flat design drawn in Truchet arcs: its side, its cells and the curves its arcs join into.9///10/// Cell `(x, y)` is column `x` and row `y`, row 0 at the bottom, stored at `y side + x`. Every cell carries two quarter arcs, each joining the midpoints of two adjacent edges: a filled cell takes the arcs around its lower-left and upper-right corners, a deleted cell the arcs around its lower-right and upper-left corners. The lower arc of a cell is the one ending on its bottom edge, the upper arc the one ending on its top edge. Arcs meeting at an edge midpoint join into curves, closed loops and open strands.11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]12pub struct Arcs {13    /// The cells per axis.14    pub side: usize,15    /// One byte a cell: bit 0 set when the cell is filled, bit 1 when its lower arc lies on a loop, bit 2 when its upper arc does.16    pub cells: Vec<u8>,17    /// The closed loops.18    pub loops: u64,19    /// The open strands, `2 side` at every level: each of the `4 side` boundary midpoints ends one.20    pub strands: u64,21}2223struct Dsu {24    parent: Vec<usize>,25}2627impl Dsu {28    fn find(&mut self, mut i: usize) -> usize {29        while self.parent[i] != i {30            self.parent[i] = self.parent[self.parent[i]];31            i = self.parent[i];32        }33        i34    }3536    fn union(&mut self, a: usize, b: usize) {37        let (ra, rb) = (self.find(a), self.find(b));38        self.parent[ra] = rb;39    }40}4142/// Draws a `side x side` grid of filled and deleted cells in arcs and counts its curves by union-find over the edge midpoints, a curve being a loop when no midpoint of it lies on the boundary.43///44/// # Errors45///46/// Errors on side zero or a cell list that is not `side^2` long.47pub fn trace(side: usize, on: &[bool]) -> Result<Arcs> {48    if side == 0 || on.len() != side * side {49        return value_error("arcs need side^2 cells and a side of at least one.");50    }51    let rows = side * (side + 1);52    let across = |x: usize, y: usize| y * side + x;53    let upright = |x: usize, y: usize| rows + y * (side + 1) + x;54    let pairs = |x: usize, y: usize| {55        let (bottom, top) = (across(x, y), across(x, y + 1));56        let (left, right) = (upright(x, y), upright(x + 1, y));57        if on[y * side + x] {58            [(bottom, left), (top, right)]59        } else {60            [(bottom, right), (top, left)]61        }62    };63    let mut dsu = Dsu {64        parent: (0..2 * rows).collect(),65    };66    let mut ends = vec![0u8; 2 * rows];67    for y in 0..side {68        for x in 0..side {69            for (a, b) in pairs(x, y) {70                ends[a] += 1;71                ends[b] += 1;72                dsu.union(a, b);73            }74        }75    }76    let mut open = vec![false; 2 * rows];77    let mut root = vec![false; 2 * rows];78    for (point, &count) in ends.iter().enumerate() {79        let r = dsu.find(point);80        root[r] = true;81        open[r] |= count == 1;82    }83    let strands = (0..2 * rows).filter(|&r| root[r] && open[r]).count() as u64;84    let loops = (0..2 * rows).filter(|&r| root[r] && !open[r]).count() as u64;85    let mut cells = vec![0u8; side * side];86    for y in 0..side {87        for x in 0..side {88            let [(lower, _), (upper, _)] = pairs(x, y);89            let mut byte = u8::from(on[y * side + x]);90            if !open[dsu.find(lower)] {91                byte |= 2;92            }93            if !open[dsu.find(upper)] {94                byte |= 4;95            }96            cells[y * side + x] = byte;97        }98    }99    Ok(Arcs {100        side,101        cells,102        loops,103        strands,104    })105}106107/// Draws level `level` of `bang dim 2, base b, code c` in arcs: cell `(x, y)` of the `b x b` mask is filled when bit `b y + x` of the code is set, level `level` is its Kronecker power built by [`crate::math::bang::factory::create`], and level 0 is one filled cell.108///109/// # Errors110///111/// Errors on a code outside the digit space of the base, or a side past the machine word.112pub fn draw(code: Code, base: usize, level: usize) -> Result<Arcs> {113    code_to_corners(code, 2, base)?;114    let Some(side) = u32::try_from(level).ok().and_then(|n| base.checked_pow(n)) else {115        return overflow_error(format!("base {base} at level {level} is too wide to draw."));116    };117    if level == 0 {118        return trace(1, &[true]);119    }120    let tensor = create(code, base, 2, base, level)?;121    let on: Vec<bool> = (0..side * side).map(|f| tensor.at(f) == 1).collect();122    trace(side, &on)123}124125// THE LAWS126127/// A proved closed form of a design's loop count, read at one level.128#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]129pub struct Law {130    /// The closed form in the level `n`, one line of text.131    pub formula: String,132    /// The loops the law gives at the level asked.133    pub loops: u64,134}135136/// Returns the proved loop law of `bang dim 2, base b, code c` at the level, or none where no law is proved: the carpet, base 3 code 495, has `(8^n - 1)/7 - 3^n + n + 1` loops; at base 2 codes 7 and 14 have `3^(n-1) - 2^n + 1`, codes 11 and 13 have `3^(n-1) - 2^(n-1)`, both from level 1 on, code 9 has `2^n - 1`, and the other eleven codes never loop.137///138/// # Errors139///140/// Errors on a code outside the digit space of the base, or a count past 64 bits.141pub fn law(code: Code, base: usize, level: usize) -> Result<Option<Law>> {142    code_to_corners(code, 2, base)?;143    let Ok(n) = u32::try_from(level) else {144        return overflow_error(format!("level {level} is past any loop law."));145    };146    let power = |b: i128, e: u32| b.checked_pow(e);147    let value = |terms: &[Option<i128>]| -> Option<i128> {148        terms149            .iter()150            .try_fold(0i128, |sum, term| sum.checked_add((*term)?))151    };152    let (formula, loops) = match (base, code.get()) {153        (3, 495) => (154            "(8^n - 1)/7 - 3^n + n + 1",155            power(8, n).map(|p| (p - 1) / 7).and_then(|head| {156                value(&[Some(head), power(3, n).map(|p| -p), Some(i128::from(n) + 1)])157            }),158        ),159        (2, 7 | 14) if n == 0 => ("3^(n-1) - 2^n + 1, n >= 1", Some(0)),160        (2, 7 | 14) => (161            "3^(n-1) - 2^n + 1, n >= 1",162            value(&[power(3, n - 1), power(2, n).map(|p| -p), Some(1)]),163        ),164        (2, 11 | 13) if n == 0 => ("3^(n-1) - 2^(n-1), n >= 1", Some(0)),165        (2, 11 | 13) => (166            "3^(n-1) - 2^(n-1), n >= 1",167            value(&[power(3, n - 1), power(2, n - 1).map(|p| -p)]),168        ),169        (2, 9) => ("2^n - 1", power(2, n).map(|p| p - 1)),170        (2, _) => ("0", Some(0)),171        _ => return Ok(None),172    };173    match loops.and_then(|count| u64::try_from(count).ok()) {174        Some(loops) => Ok(Some(Law {175            formula: formula.to_string(),176            loops,177        })),178        None => overflow_error(format!("the loop law passes 64 bits at level {level}.")),179    }180}181182#[cfg(test)]183mod tests {184    use super::*;185186    fn loops(code: u128, base: usize, levels: std::ops::RangeInclusive<usize>) -> Vec<u64> {187        levels188            .map(|n| draw(Code::from(code), base, n).unwrap().loops)189            .collect()190    }191192    #[test]193    fn the_carpet_and_code_seven_close_their_loops() {194        assert_eq!(loops(495, 3, 1..=4), [0, 3, 50, 509]);195        assert_eq!(loops(7, 2, 1..=4), [0, 0, 2, 12]);196    }197198    #[test]199    fn every_proved_law_meets_the_count_and_strands_are_twice_the_side() {200        let mut cases: Vec<(u128, usize, usize)> = (0..16).map(|code| (code, 2, 7)).collect();201        cases.push((495, 3, 5));202        for (code, base, top) in cases {203            for n in 0..=top {204                let arcs = draw(Code::from(code), base, n).unwrap();205                let law = law(Code::from(code), base, n).unwrap().unwrap();206                assert_eq!(law.loops, arcs.loops, "code {code} base {base} level {n}");207                assert_eq!(arcs.strands, 2 * arcs.side as u64);208            }209        }210        assert_eq!(law(Code::from(511u128), 3, 2).unwrap(), None);211        assert!(law(Code::from(16u128), 2, 2).is_err());212    }213214    #[test]215    fn a_cell_byte_marks_the_filled_cell_and_its_arcs_on_loops() {216        let arcs = draw(Code::from(9u128), 2, 1).unwrap();217        assert_eq!((arcs.side, arcs.loops, arcs.strands), (2, 1, 4));218        assert_eq!(arcs.cells, [5, 4, 2, 3]);219        assert_eq!(draw(Code::from(1u128), 2, 0).unwrap().cells, [1]);220        assert!(trace(2, &[true; 3]).is_err());221    }222}