census.rs

13.7 kB · rust · 389 lines

1use super::graph::edge_graph;2use super::models::Cell3d;3use crate::dim::census;4use mrlycore::errors::Result;56pub use crate::dim::census::{edges, vertices};78/// The tally of a cube's sites.9#[derive(Clone, Debug, PartialEq, Eq)]10pub struct Census {11    /// The count of filled sites.12    pub fills: usize,13    /// The count of empty sites.14    pub voids: usize,15    /// The count of exposed faces.16    pub surface: u128,17    /// The count of corners the filled sites touch.18    pub vertices: usize,19    /// The count of unit edges the filled sites touch.20    pub edges: usize,21    /// The count of unit faces the filled sites touch, shared ones counted once.22    pub faces: usize,23    /// The Euler characteristic of the filled complex.24    pub euler: i64,25}2627/// Returns the count of filled sites.28pub fn fills(cell: &Cell3d) -> usize {29    census::fills(cell)30}3132/// Returns the count of empty sites.33pub fn voids(cell: &Cell3d) -> usize {34    census::voids(cell)35}3637/// Returns the filled-site count, the cube's volume.38pub fn volume(cell: &Cell3d) -> usize {39    fills(cell)40}4142/// Returns the count of filled faces exposed to void or the outside.43pub fn surface(cell: &Cell3d) -> u128 {44    census::exposure(cell)45}4647/// Returns the count of faces buried between two filled sites, six per site less the exposed surface.48///49/// ```50/// let block = mrlymath::three::ones(2, 1).unwrap();51/// assert_eq!(mrlymath::three::census::hidden(&block), 24);52/// ```53pub fn hidden(cell: &Cell3d) -> u128 {54    6 * fills(cell) as u128 - surface(cell)55}5657/// Returns the count of unit faces the filled sites touch, a face shared by two sites counted once.58///59/// Surface counts only the faces open to void; this counts every face of the complex.60///61/// ```62/// let block = mrlymath::three::ones(2, 1).unwrap();63/// assert_eq!(mrlymath::three::census::faces(&block), 36);64/// assert_eq!(mrlymath::three::census::surface(&block), 24);65/// ```66pub fn faces(cell: &Cell3d) -> usize {67    let grid = cell.types();68    let (dx, dy, dz) = (grid.shape[0], grid.shape[1], grid.shape[2]);69    let mut planes = [70        vec![false; (dx + 1) * dy * dz],71        vec![false; dx * (dy + 1) * dz],72        vec![false; dx * dy * (dz + 1)],73    ];74    for i in 0..dx {75        for j in 0..dy {76            for k in 0..dz {77                if grid.get(&[i, j, k]) == 0 {78                    continue;79                }80                for step in 0..2 {81                    planes[0][((i + step) * dy + j) * dz + k] = true;82                    planes[1][(i * (dy + 1) + j + step) * dz + k] = true;83                    planes[2][(i * dy + j) * (dz + 1) + k + step] = true;84                }85            }86        }87    }88    planes.iter().flatten().filter(|&&on| on).count()89}9091/// Returns the Euler characteristic of the filled complex, vertices less edges plus faces less sites.92///93/// One for a solid block, and one less than twice the genus below zero for a tunnelled one.94///95/// ```96/// assert_eq!(mrlymath::three::census::euler(&mrlymath::three::ones(2, 1).unwrap()).unwrap(), 1);97/// assert_eq!(mrlymath::three::census::euler(&mrlymath::three::carpet(3, 1).unwrap()).unwrap(), -4);98/// ```99pub fn euler(cell: &Cell3d) -> Result<i64> {100    let net = edge_graph(cell)?;101    let (v, e) = (net.nodes.len() as i64, net.branches.len() as i64);102    Ok(v - e + faces(cell) as i64 - fills(cell) as i64)103}104105/// Tallies a cell's sites, its exposed surface and its Euler characteristic in one reading.106///107/// ```108/// let tally = mrlymath::three::census::census(&mrlymath::three::carpet(3, 1).unwrap()).unwrap();109/// assert_eq!((tally.fills, tally.voids, tally.surface), (20, 7, 72));110/// assert_eq!((tally.vertices, tally.edges, tally.faces, tally.euler), (64, 144, 96, -4));111/// ```112pub fn census(cell: &Cell3d) -> Result<Census> {113    let net = edge_graph(cell)?;114    let (vertices, edges) = (net.nodes.len(), net.branches.len());115    let (fills, faces) = (fills(cell), faces(cell));116    Ok(Census {117        fills,118        voids: voids(cell),119        surface: surface(cell),120        vertices,121        edges,122        faces,123        euler: vertices as i64 - edges as i64 + faces as i64 - fills as i64,124    })125}126127#[cfg(test)]128mod tests {129    use super::*;130    use crate::formulas;131    use crate::three::designs;132    #[test]133    fn census_matches_formulas() {134        for code in [23u128, 129, 17, 232] {135            for level in 1..3u32 {136                let cell = designs::create(code, 3, level as usize, 2).unwrap();137                assert_eq!(138                    fills(&cell) as u128,139                    formulas::fill(code, 3, 3, level, 2).unwrap()140                );141                assert_eq!(142                    surface(&cell),143                    formulas::surface(code, 3, level, 2).unwrap(),144                    "code={code} l={level}"145                );146            }147        }148    }149    #[test]150    fn menger_census() {151        let c = designs::carpet(3, 1).unwrap();152        let result = census(&c).unwrap();153        assert_eq!(result.fills, 20);154        assert_eq!(result.voids, 7);155        assert_eq!(result.surface, 72);156        assert_eq!(result.vertices, 64);157        assert_eq!(result.edges, 144);158        assert_eq!(result.faces, 96);159        assert_eq!(result.euler, -4);160    }161    #[test]162    fn a_solid_block_is_contractible() {163        for n in [1, 2, 3] {164            let block = designs::ones(n, 1).unwrap();165            assert_eq!(euler(&block).unwrap(), 1, "n={n}");166        }167        let one = census(&designs::ones(1, 1).unwrap()).unwrap();168        assert_eq!((one.vertices, one.edges, one.faces), (8, 12, 6));169    }170    #[test]171    fn the_sponge_deepens_its_genus() {172        let level_two = census(&designs::carpet(3, 2).unwrap()).unwrap();173        assert_eq!(level_two.fills, 400);174        assert_eq!(level_two.vertices, 896);175        assert_eq!(level_two.edges, 2304);176        assert_eq!(level_two.faces, 1728);177        assert_eq!(level_two.euler, -80);178    }179    #[test]180    fn the_parts_agree_with_the_whole() {181        for cell in [182            designs::net(3, 1).unwrap(),183            designs::void(4, 1).unwrap(),184            designs::xtree(3, 2).unwrap(),185        ] {186            let tally = census(&cell).unwrap();187            assert_eq!(tally.vertices, vertices(&cell).unwrap());188            assert_eq!(tally.edges, edges(&cell).unwrap());189            assert_eq!(tally.faces, faces(&cell));190            assert_eq!(tally.euler, euler(&cell).unwrap());191            assert_eq!(tally.fills, volume(&cell));192        }193    }194}195196#[cfg(test)]197mod theorems {198    use super::*;199    use crate::bang::universe::{orbit, total_exposure, touches_every_corner};200    use crate::three::designs;201    use crate::two;202    use std::collections::BTreeSet;203204    fn tile_fill(code: u128, number: usize) -> u128 {205        fills(&designs::create(code, number, 1, 2).unwrap()) as u128206    }207208    fn state(code: u128, number: usize, level: usize) -> (i128, i128) {209        let cell = designs::create(code, number, level, 2).unwrap();210        (surface(&cell) as i128, hidden(&cell) as i128)211    }212213    fn second_eigenvalue(code: u128, number: usize) -> i128 {214        let half = (number / 2) as i128;215        let side = number as i128;216        match code {217            23 => side * side - half * half,218            232 => half * half,219            _ => (side - half) * (side - half),220        }221    }222223    #[test]224    fn the_face_ledger_prints_the_family_closed_forms() {225        let visible = [226            (23u128, [72u128, 1056, 18048, 336384]),227            (232, [30, 198, 1374, 9606]),228            (3, [56, 608, 7040, 83456]),229            (129, [54, 486, 4374, 39366]),230        ];231        for (code, faces) in visible {232            let fc = tile_fill(code, 3);233            for (index, &want) in faces.iter().enumerate() {234                let level = index + 1;235                let cell = designs::create(code, 3, level, 2).unwrap();236                assert_eq!(surface(&cell), want, "code={code} l={level}");237                assert_eq!(238                    hidden(&cell),239                    6 * fc.pow(level as u32) - want,240                    "code={code} l={level}"241                );242            }243        }244        let carpet: Vec<u128> = (1..5)245            .map(|level| hidden(&designs::create(23, 3, level, 2).unwrap()))246            .collect();247        assert_eq!(carpet, [48, 1344, 29952, 623616]);248    }249250    #[test]251    fn the_face_matrix_fits_its_eigenvalues_and_predicts() {252        let mut fitted = 0;253        for (number, top) in [(3usize, 4usize), (5, 3), (7, 2)] {254            for code in [23u128, 232, 3, 129] {255                let fc = tile_fill(code, number) as i128;256                let l2 = second_eigenvalue(code, number);257                let states: Vec<(i128, i128)> =258                    (1..=top).map(|level| state(code, number, level)).collect();259                let work = states[0].1 / 2;260                for (index, &(v, h)) in states.iter().enumerate() {261                    let level = index as u32 + 1;262                    assert_eq!(v + h, 6 * fc.pow(level), "code={code} n={number} l={level}");263                    let want = if work == 0 {264                        0265                    } else {266                        2 * work * (fc.pow(level) - l2.pow(level)) / (fc - l2)267                    };268                    assert_eq!(h, want, "code={code} n={number} l={level}");269                }270                if top < 3 || work == 0 {271                    continue;272                }273                let ((v1, h1), (v2, h2), (v3, h3)) = (states[0], states[1], states[2]);274                let base = v1 * h2 - v2 * h1;275                assert_eq!(276                    v1 * h3 - h1 * v3,277                    (fc + l2) * base,278                    "code={code} n={number}"279                );280                assert_eq!(v2 * h3 - v3 * h2, fc * l2 * base, "code={code} n={number}");281                if code == 23 && number == 3 {282                    let entries = [283                        v2 * h2 - v3 * h1,284                        v1 * v3 - v2 * v2,285                        h2 * h2 - h3 * h1,286                        v1 * h3 - v2 * h2,287                    ];288                    for entry in entries {289                        assert_eq!(entry % base, 0, "code={code} n={number}");290                    }291                    let matrix = [292                        [entries[0] / base, entries[1] / base],293                        [entries[2] / base, entries[3] / base],294                    ];295                    assert_eq!(matrix, [[12, 4], [8, 16]]);296                }297                let numerator = (v2 * h2 - v3 * h1) * v3 + (v3 * v1 - v2 * v2) * h3;298                assert_eq!(numerator % base, 0, "code={code} n={number}");299                let next = numerator / base;300                let closed = 6 * fc.pow(4) - 2 * work * (fc.pow(4) - l2.pow(4)) / (fc - l2);301                assert_eq!(next, closed, "code={code} n={number}");302                if number == 3 {303                    assert_eq!(next, states[3].0, "code={code}");304                }305                fitted += 1;306            }307        }308        assert_eq!(fitted, 6);309    }310311    #[test]312    fn the_void_buries_no_face() {313        for k in 1..13u128 {314            let number = 2 * k as usize - 1;315            let cell = designs::create(129, number, 1, 2).unwrap();316            let cells = k * k * k + (k - 1) * (k - 1) * (k - 1);317            assert_eq!(fills(&cell) as u128, cells, "k={k}");318            assert_eq!(surface(&cell), 6 * cells, "k={k}");319            assert_eq!(hidden(&cell), 0, "k={k}");320            let flat = two::create(9, number, 1, 0, 2).unwrap();321            let tally = two::census::census(&flat).unwrap();322            assert_eq!(tally.edges as u128, tally.perimeter, "k={k}");323        }324        for level in 1..5u32 {325            let cell = designs::create(129, 3, level as usize, 2).unwrap();326            assert_eq!(surface(&cell), 6 * 9u128.pow(level), "l={level}");327        }328    }329330    #[test]331    fn total_exposure_holds_for_the_independent_corner_sets() {332        for number in [3usize, 5, 7] {333            for code in 0..256u128 {334                let cell = designs::create(code, number, 1, 2).unwrap();335                let open = surface(&cell) == 6 * fills(&cell) as u128;336                assert_eq!(open, total_exposure(code, 3), "code={code} n={number}");337            }338        }339        let exposed: Vec<u128> = (0..256).filter(|&c| total_exposure(c, 3)).collect();340        let classes: BTreeSet<u128> = exposed341            .iter()342            .map(|&c| *orbit(c, 3).iter().next().unwrap())343            .collect();344        assert_eq!(exposed.len(), 35);345        assert_eq!(346            classes.into_iter().collect::<Vec<u128>>(),347            [0, 1, 6, 22, 24, 105]348        );349    }350351    #[test]352    fn the_all_even_rule_touches_every_grid_corner() {353        for k in 1..4usize {354            let number = 2 * k - 1;355            let grid = (number + 1).pow(3);356            for code in 0..256u128 {357                let cell = designs::create(code, number, 1, 2).unwrap();358                let whole = vertices(&cell).unwrap() == grid;359                assert_eq!(whole, touches_every_corner(code, 3), "code={code} k={k}");360            }361        }362        assert_eq!(363            (0..256u128).filter(|&c| touches_every_corner(c, 3)).count(),364            128365        );366    }367368    #[test]369    fn the_net_falls_short_of_the_grid_corners() {370        for k in 1..21usize {371            let cell = designs::create(232, 2 * k - 1, 1, 2).unwrap();372            let m = k - 1;373            assert_eq!(vertices(&cell).unwrap(), 8 * m * m * (k + 2), "k={k}");374            assert_eq!(375                8 * k * k * k - vertices(&cell).unwrap(),376                24 * k - 16,377                "k={k}"378            );379        }380        for k in 1..25usize {381            let flat = two::net(2 * k - 1, 1).unwrap();382            assert_eq!(383                two::census::vertices(&flat).unwrap(),384                4 * k * k - 4,385                "k={k}"386            );387        }388    }389}