counting.rs

8.1 kB · rust · 231 lines

1use crate::core::error::{value_error, Result};2use crate::math::bang::factory;3use crate::math::bang::Code;4use crate::num::factor::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.40///41/// ```42/// use mrlyrs::math::bang::Code;43/// assert_eq!(mrlyrs::math::counts::fill(Code::from(7u64), 3, 2, 2, 2).unwrap(), 64);44/// ```45///46/// # Errors47///48/// Errors when the code is out of range for the dimension and base.49pub fn fill(code: Code, number: usize, dimension: usize, level: u32, base: usize) -> Result<u128> {50    let filled = factory::code_to_corners(code, dimension, base)?;51    Ok(fill_from_corners(&filled, number, dimension, level, base))52}5354/// Returns the empty cell count, grid minus fill.55///56/// # Errors57///58/// Errors when the code is out of range for the dimension and base.59pub fn void(code: Code, number: usize, dimension: usize, level: u32, base: usize) -> Result<u128> {60    Ok(grid(number, dimension, level) - fill(code, number, dimension, level, base)?)61}6263/// Returns the filled fraction of the grid, or 0.0 for an empty grid.64///65/// # Errors66///67/// Errors when the code is out of range for the dimension and base.68pub fn ratio(code: Code, number: usize, dimension: usize, level: u32, base: usize) -> Result<f64> {69    let total = grid(number, dimension, level);70    if total == 0 {71        return Ok(0.0);72    }73    Ok(fill(code, number, dimension, level, base)? as f64 / total as f64)74}7576/// Returns the exact filled fraction as a fraction of fill over grid, reduced.77///78/// ```79/// use mrlyrs::math::bang::Code;80/// assert_eq!(mrlyrs::math::counts::rational(Code::from(7u64), 3, 2, 2, 2).unwrap(), (64, 81));81/// ```82///83/// # Errors84///85/// Errors when the code is out of range for the dimension and base.86pub fn rational(87    code: Code,88    number: usize,89    dimension: usize,90    level: u32,91    base: usize,92) -> Result<(u128, u128)> {93    let total = grid(number, dimension, level);94    if total == 0 {95        return Ok((0, 1));96    }97    Ok(reduce(fill(code, number, dimension, level, base)?, total))98}99100/// Returns the fill ratio the code walks toward as the side number grows, reduced.101///102/// The ratio is the corner count over the corner slots, so it is always rational: the103/// carpet holds three corners of four and the sponge four of eight.104///105/// ```106/// use mrlyrs::math::bang::Code;107/// assert_eq!(mrlyrs::math::counts::limit(Code::from(7u64), 2, 1, 2).unwrap(), (3, 4));108/// assert_eq!(mrlyrs::math::counts::limit(Code::from(7u64), 2, 2, 2).unwrap(), (9, 16));109/// ```110///111/// # Errors112///113/// Errors when the code is out of range, or the limit overflows at this level.114pub fn limit(code: Code, dimension: usize, level: u32, base: usize) -> Result<(u128, u128)> {115    let corners = factory::code_to_corners(code, dimension, base)?.len() as u128;116    let slots = (base as u128).pow(dimension as u32);117    let overflow = || value_error("fill ratio limit overflows at this level.");118    let (Some(top), Some(bottom)) = (corners.checked_pow(level), slots.checked_pow(level)) else {119        return overflow();120    };121    Ok(reduce(top, bottom))122}123124/// Returns the code's fractal dimension, the log of its one-level fill over the log of number.125///126/// # Errors127///128/// Errors when the code is out of range for the dimension and base.129pub fn dimension(code: Code, number: usize, base_dimension: usize, base: usize) -> Result<f64> {130    if number == 1 {131        return Ok(base_dimension as f64);132    }133    let f = fill(code, number, base_dimension, 1, base)?;134    if f == 0 {135        return Ok(0.0);136    }137    Ok((f as f64).ln() / (number as f64).ln())138}139140#[cfg(test)]141mod tests {142    use super::*;143    use crate::math::bang::factory;144    #[test]145    fn fill_matches_rendered_sum() {146        for dimension in 2..=3usize {147            for code in [Code(0), Code(1), Code(7), Code(23), Code(100)] {148                if code >= factory::total_codes(dimension, 2).unwrap() {149                    continue;150                }151                for number in 1..6 {152                    for level in 1..3 {153                        let rendered =154                            factory::create(code, number, dimension, 2, level as usize).unwrap();155                        assert_eq!(156                            fill(code, number, dimension, level, 2).unwrap(),157                            rendered.sum() as u128,158                            "code={code} d={dimension} n={number} l={level}"159                        );160                    }161                }162            }163        }164    }165    #[test]166    fn menger_dimension() {167        let d = dimension(Code(23), 3, 3, 2).unwrap();168        assert!((d - 2.7268).abs() < 0.001);169    }170    #[test]171    fn rational_reduces_the_exact_fraction() {172        assert_eq!(rational(Code(7), 3, 2, 1, 2).unwrap(), (8, 9));173        assert_eq!(rational(Code(7), 3, 2, 3, 2).unwrap(), (512, 729));174        assert_eq!(rational(Code(15), 5, 2, 2, 2).unwrap(), (1, 1));175        assert_eq!(rational(Code(0), 5, 2, 2, 2).unwrap(), (0, 1));176        assert_eq!(rational(Code(7), 0, 2, 1, 2).unwrap(), (0, 1));177        for number in 1..6usize {178            let (top, bottom) = rational(Code(23), number, 3, 2, 2).unwrap();179            let exact = fill(Code(23), number, 3, 2, 2).unwrap() as f64 / grid(number, 3, 2) as f64;180            assert!(181                (top as f64 / bottom as f64 - exact).abs() < 1e-12,182                "n={number}"183            );184        }185    }186    #[test]187    fn the_carpet_and_sponge_limits_stay_rational() {188        assert_eq!(limit(Code(7), 2, 1, 2).unwrap(), (3, 4));189        let sponge: Vec<Vec<u8>> = factory::residue_corners(3, 2)190            .into_iter()191            .filter(|corner| corner.iter().filter(|&&r| r == 1).count() <= 1)192            .collect();193        let code = factory::corners_to_code(&sponge, 3, 2);194        assert_eq!(limit(code, 3, 1, 2).unwrap(), (1, 2));195        assert_eq!(limit(code, 3, 3, 2).unwrap(), (1, 8));196        assert_eq!(limit(Code(0), 2, 1, 2).unwrap(), (0, 1));197        assert_eq!(limit(Code(15), 2, 4, 2).unwrap(), (1, 1));198    }199    #[test]200    fn refuses_a_code_past_its_range_and_a_level_past_a_u128() {201        assert!(fill(Code(16), 3, 2, 1, 2).is_err());202        assert!(void(Code(16), 3, 2, 1, 2).is_err());203        assert!(ratio(Code(16), 3, 2, 1, 2).is_err());204        assert!(rational(Code(16), 3, 2, 1, 2).is_err());205        assert!(dimension(Code(16), 3, 2, 2).is_err());206        assert!(fill(Code(1), 3, 7, 1, 2).is_err());207        assert!(limit(Code(16), 2, 1, 2).is_err());208        assert!(limit(Code(7), 2, 1000, 2).is_err());209    }210    #[test]211    fn wide_grids_walk_toward_the_limit() {212        let (top, bottom) = limit(Code(7), 2, 1, 2).unwrap();213        let target = top as f64 / bottom as f64;214        let mut last = f64::MAX;215        for number in [3usize, 9, 27, 81, 243] {216            let gap = (ratio(Code(7), number, 2, 1, 2).unwrap() - target).abs();217            assert!(gap < last, "n={number} gap {gap} did not shrink");218            last = gap;219        }220        assert!(last < 0.01);221    }222    #[test]223    fn fill_plus_void_is_grid() {224        for bits in 0..16u128 {225            let code = Code(bits);226            let f = fill(code, 4, 2, 2, 2).unwrap();227            let v = void(code, 4, 2, 2, 2).unwrap();228            assert_eq!(f + v, grid(4, 2, 2));229        }230    }231}