counting.rs

6.7 kB · rust · 189 lines

1use crate::bang::factory;2use crate::bang::universe::Code;3use mrlycore::errors::{value_error, Result};4use mrlynum::classics::reduce;56/// Counts the indices below number that equal residue modulo base.7pub fn positions(residue: usize, number: usize, base: usize) -> u128 {8    if residue >= number {9        return 0;10    }11    (number - residue).div_ceil(base) as u12812}1314/// Returns the total cells of the grid, number to the dimension, to the level.15pub fn grid(number: usize, dimension: usize, level: u32) -> u128 {16    (number as u128).pow(dimension as u32).pow(level)17}1819/// Sums each corner's position products into a base fill and raises it to the level.20pub fn fill_from_corners(21    filled: &[Vec<u8>],22    number: usize,23    _dimension: usize,24    level: u32,25    base: usize,26) -> u128 {27    let base_fill: u128 = filled28        .iter()29        .map(|corner| {30            corner31                .iter()32                .map(|&r| positions(r as usize, number, base))33                .product::<u128>()34        })35        .sum();36    base_fill.pow(level)37}3839/// Returns the filled cell count of the code's fractal at the given level, without rendering it.40pub fn fill(code: Code, number: usize, dimension: usize, level: u32, base: usize) -> Result<u128> {41    let filled = factory::code_to_corners(code, dimension, base)?;42    Ok(fill_from_corners(&filled, number, dimension, level, base))43}4445/// Returns the empty cell count, grid minus fill.46pub fn void(code: Code, number: usize, dimension: usize, level: u32, base: usize) -> Result<u128> {47    Ok(grid(number, dimension, level) - fill(code, number, dimension, level, base)?)48}4950/// Returns the filled fraction of the grid, or 0.0 for an empty grid.51pub fn ratio(code: Code, number: usize, dimension: usize, level: u32, base: usize) -> Result<f64> {52    let total = grid(number, dimension, level);53    if total == 0 {54        return Ok(0.0);55    }56    Ok(fill(code, number, dimension, level, base)? as f64 / total as f64)57}5859/// Returns the exact filled fraction as a fraction of fill over grid, reduced.60///61/// ```62/// assert_eq!(mrlymath::formulas::rational(7, 3, 2, 2, 2).unwrap(), (64, 81));63/// ```64pub fn rational(65    code: Code,66    number: usize,67    dimension: usize,68    level: u32,69    base: usize,70) -> Result<(u128, u128)> {71    let total = grid(number, dimension, level);72    if total == 0 {73        return Ok((0, 1));74    }75    Ok(reduce(fill(code, number, dimension, level, base)?, total))76}7778/// Returns the fill ratio the code walks toward as the side number grows, reduced.79///80/// The ratio is the corner count over the corner slots, so it is always rational: the81/// carpet holds three corners of four and the sponge four of eight.82///83/// ```84/// assert_eq!(mrlymath::formulas::limit(7, 2, 1, 2).unwrap(), (3, 4));85/// assert_eq!(mrlymath::formulas::limit(7, 2, 2, 2).unwrap(), (9, 16));86/// ```87pub fn limit(code: Code, dimension: usize, level: u32, base: usize) -> Result<(u128, u128)> {88    let corners = factory::code_to_corners(code, dimension, base)?.len() as u128;89    let slots = (base as u128).pow(dimension as u32);90    let overflow = || value_error("fill ratio limit overflows at this level.");91    let (Some(top), Some(bottom)) = (corners.checked_pow(level), slots.checked_pow(level)) else {92        return overflow();93    };94    Ok(reduce(top, bottom))95}9697/// Returns the code's fractal dimension, the log of its one-level fill over the log of number.98pub fn dimension(code: Code, number: usize, base_dimension: usize, base: usize) -> Result<f64> {99    if number == 1 {100        return Ok(base_dimension as f64);101    }102    let f = fill(code, number, base_dimension, 1, base)?;103    if f == 0 {104        return Ok(0.0);105    }106    Ok((f as f64).ln() / (number as f64).ln())107}108109#[cfg(test)]110mod tests {111    use super::*;112    use crate::bang::factory;113    #[test]114    fn fill_matches_rendered_sum() {115        for dimension in 2..=3usize {116            for code in [0u128, 1, 7, 23, 100] {117                if code >= factory::total_codes(dimension, 2) {118                    continue;119                }120                for number in 1..6 {121                    for level in 1..3 {122                        let rendered =123                            factory::create(code, number, dimension, 2, level as usize).unwrap();124                        assert_eq!(125                            fill(code, number, dimension, level, 2).unwrap(),126                            rendered.sum() as u128,127                            "code={code} d={dimension} n={number} l={level}"128                        );129                    }130                }131            }132        }133    }134    #[test]135    fn menger_dimension() {136        let d = dimension(23, 3, 3, 2).unwrap();137        assert!((d - 2.7268).abs() < 0.001);138    }139    #[test]140    fn rational_reduces_the_exact_fraction() {141        assert_eq!(rational(7, 3, 2, 1, 2).unwrap(), (8, 9));142        assert_eq!(rational(7, 3, 2, 3, 2).unwrap(), (512, 729));143        assert_eq!(rational(15, 5, 2, 2, 2).unwrap(), (1, 1));144        assert_eq!(rational(0, 5, 2, 2, 2).unwrap(), (0, 1));145        assert_eq!(rational(7, 0, 2, 1, 2).unwrap(), (0, 1));146        for number in 1..6usize {147            let (top, bottom) = rational(23, number, 3, 2, 2).unwrap();148            let exact = fill(23, number, 3, 2, 2).unwrap() as f64 / grid(number, 3, 2) as f64;149            assert!(150                (top as f64 / bottom as f64 - exact).abs() < 1e-12,151                "n={number}"152            );153        }154    }155    #[test]156    fn the_carpet_and_sponge_limits_stay_rational() {157        assert_eq!(limit(7, 2, 1, 2).unwrap(), (3, 4));158        let sponge: Vec<Vec<u8>> = factory::residue_corners(3, 2)159            .into_iter()160            .filter(|corner| corner.iter().filter(|&&r| r == 1).count() <= 1)161            .collect();162        let code = factory::corners_to_code(&sponge, 3, 2);163        assert_eq!(limit(code, 3, 1, 2).unwrap(), (1, 2));164        assert_eq!(limit(code, 3, 3, 2).unwrap(), (1, 8));165        assert_eq!(limit(0, 2, 1, 2).unwrap(), (0, 1));166        assert_eq!(limit(15, 2, 4, 2).unwrap(), (1, 1));167        assert!(limit(7, 2, 1000, 2).is_err());168    }169    #[test]170    fn wide_grids_walk_toward_the_limit() {171        let (top, bottom) = limit(7, 2, 1, 2).unwrap();172        let target = top as f64 / bottom as f64;173        let mut last = f64::MAX;174        for number in [3usize, 9, 27, 81, 243] {175            let gap = (ratio(7, number, 2, 1, 2).unwrap() - target).abs();176            assert!(gap < last, "n={number} gap {gap} did not shrink");177            last = gap;178        }179        assert!(last < 0.01);180    }181    #[test]182    fn fill_plus_void_is_grid() {183        for code in 0..16u128 {184            let f = fill(code, 4, 2, 2, 2).unwrap();185            let v = void(code, 4, 2, 2, 2).unwrap();186            assert_eq!(f + v, grid(4, 2, 2));187        }188    }189}