terms.rs

11.8 kB · rust · 316 lines

1use super::{Axis, Closed, Key, Measure};2use mrlycore::errors::{value_error, Result};3use mrlycore::tensor::Tensor;4use mrlymath::bang::{code_to_corners, factory};5use mrlymath::formulas;6use mrlymath::{six, three, two};78fn binomial(n: usize, k: usize) -> i128 {9    (0..k).fold(1i128, |acc, i| acc * (n - i) as i128 / (i as i128 + 1))10}1112fn grid(number: usize, dimension: usize, level: u32) -> Option<u128> {13    (number as u128).checked_pow((dimension as u32).checked_mul(level)?)14}1516fn tile(key: &Key, number: usize) -> Result<Tensor> {17    factory::create(key.code, number, key.dimension, key.base, 1)18}1920fn complex(key: &Key, number: usize, level: u32) -> Result<i128> {21    let Key {22        code,23        dimension,24        base,25        measure,26        ..27    } = *key;28    let value = match dimension {29        2 => {30            let tally = two::census(&two::create(code, number, level as usize, 0, base)?)?;31            match measure {32                Measure::Vertices => tally.vertices as i128,33                Measure::Edges => tally.edges as i128,34                Measure::Euler => tally.euler as i128,35                _ => return value_error("the plane carries no faces."),36            }37        }38        _ => {39            let tally = three::census(&three::create(code, number, level as usize, base)?)?;40            match measure {41                Measure::Vertices => tally.vertices as i128,42                Measure::Edges => tally.edges as i128,43                Measure::Faces => tally.faces as i128,44                _ => tally.euler as i128,45            }46        }47    };48    Ok(value)49}5051fn term(key: &Key, number: usize, level: u32, cells: u128) -> Result<Option<i128>> {52    let Key {53        code,54        dimension,55        base,56        measure,57        ..58    } = *key;59    let fill = || -> Result<Option<u128>> {60        Ok(formulas::fill(code, number, dimension, 1, base)?.checked_pow(level))61    };62    let within = |cost: Option<u128>| cost.is_some_and(|cost| cost <= cells);63    let value = match measure {64        Measure::Fills => fill()?.and_then(|fill| i128::try_from(fill).ok()),65        Measure::Voids => grid(number, dimension, level)66            .zip(fill()?)67            .map(|(grid, fill)| (grid - fill) as i128),68        Measure::Surface => {69            let corners = code_to_corners(code, dimension, base)?;70            formulas::Exposure::from_corners(&corners, number, dimension, base)71                .at(level)72                .map(|faces| faces as i128)73        }74        Measure::Peak | Measure::Heights => {75            let span = (number as u128)76                .checked_pow(level)77                .map(|side| dimension as u128 * (side - 1) + 1);78            if !within(span) {79                return Ok(None);80            }81            let counts = formulas::profile_of_tile(&tile(key, number)?, level)?;82            Some(if measure == Measure::Peak {83                counts.iter().max().copied().unwrap_or(0) as i12884            } else {85                counts.iter().filter(|&&count| count > 0).count() as i12886            })87        }88        Measure::Vertices | Measure::Edges | Measure::Faces | Measure::Euler => {89            if !within(grid(number, dimension, level)) {90                return Ok(None);91            }92            Some(complex(key, number, level)?)93        }94        Measure::Triangles => {95            let cost = (number as u128)96                .checked_pow(2 * level)97                .and_then(|side| side.checked_mul(16));98            if !within(cost) {99                return Ok(None);100            }101            Some(formulas::cut_fills(code, number, level)? as i128)102        }103        Measure::Holes | Measure::Pieces => {104            if !within(grid(number, dimension, level)) {105                return Ok(None);106            }107            let slice = six::cut(&three::create(code, number, level as usize, base)?)?;108            Some(if measure == Measure::Holes {109                six::holes(&slice)? as i128110            } else {111                six::components(&slice)? as i128112            })113        }114    };115    Ok(value)116}117118/// Reads the first terms of a design sequence within a cell budget, and whether the budget or a u128 cut them short.119///120/// ```121/// use mrlylab::ledger::{terms, Axis, Key, Measure, BUDGET};122/// let carpet = Key::new(7, 2, 2, Measure::Fills, Axis::Level);123/// assert_eq!(terms(&carpet, 3, BUDGET).unwrap(), (vec![8, 64, 512], false));124/// let sponge = Key::new(23, 3, 2, Measure::Surface, Axis::Level);125/// assert_eq!(terms(&sponge, 3, BUDGET).unwrap(), (vec![72, 1056, 18048], false));126/// ```127pub fn terms(key: &Key, count: usize, cells: u128) -> Result<(Vec<i128>, bool)> {128    code_to_corners(key.code, key.dimension, key.base)?;129    if !key.measure.applies(key.dimension, key.base) {130        return value_error(format!(131            "{} does not read dimension {} base {}.",132            key.measure.slug(),133            key.dimension,134            key.base135        ));136    }137    let mut out = Vec::with_capacity(count);138    for index in 0..count {139        let (number, level) = key.axis.place(index, key.number());140        match term(key, number, level, cells)? {141            Some(value) => out.push(value),142            None => return Ok((out, true)),143        }144    }145    Ok((out, false))146}147148/// Expands the odd-side fill at side `2k - 1`, `sum over the corners of k^(zeros) (k - 1)^(ones)`, into coefficients by rising power of `k`.149///150/// ```151/// let carpet = mrlymath::bang::code_to_corners(7, 2, 2).unwrap();152/// assert_eq!(mrlylab::ledger::fill_polynomial(&carpet, 2), [0, -2, 3]);153/// ```154pub fn fill_polynomial(corners: &[Vec<u8>], dimension: usize) -> Vec<i128> {155    let mut out = vec![0i128; dimension + 1];156    for corner in corners {157        let ones = corner.iter().filter(|&&r| r != 0).count();158        for j in 0..=ones {159            let sign = if (ones - j).is_multiple_of(2) { 1 } else { -1 };160            out[dimension - ones + j] += sign * binomial(ones, j);161        }162    }163    out164}165166fn odd_power(dimension: usize) -> Vec<i128> {167    (0..=dimension)168        .map(|j| {169            let sign = if (dimension - j).is_multiple_of(2) {170                1171            } else {172                -1173            };174            sign * binomial(dimension, j) * (1i128 << j)175        })176        .collect()177}178179/// Returns the closed form of a design sequence when the ledger knows one.180///181/// Level fills are a power and level voids a difference of powers; base-2 side fills and voids182/// are polynomials in `k` at side `2k - 1`; the level surface obeys the exposure recurrence.183pub fn closed(key: &Key) -> Result<Option<Closed>> {184    let Key {185        code,186        dimension,187        base,188        measure,189        axis,190    } = *key;191    let corners = code_to_corners(code, dimension, base)?;192    let fill = || formulas::fill(code, key.number(), dimension, 1, base);193    let form = match (measure, axis) {194        (Measure::Fills, Axis::Level) => Closed::Power(fill()?),195        (Measure::Voids, Axis::Level) => {196            Closed::Difference((key.number() as u128).pow(dimension as u32), fill()?)197        }198        (Measure::Fills, Axis::Side) if base == 2 => {199            Closed::Polynomial(fill_polynomial(&corners, dimension))200        }201        (Measure::Voids, Axis::Side) if base == 2 => Closed::Polynomial(202            odd_power(dimension)203                .iter()204                .zip(fill_polynomial(&corners, dimension))205                .map(|(all, filled)| all - filled)206                .collect(),207        ),208        (Measure::Surface, Axis::Level) => Closed::Recurrence(209            formulas::Exposure::from_corners(&corners, key.number(), dimension, base).recurrence(),210        ),211        _ => return Ok(None),212    };213    Ok(Some(form))214}215216#[cfg(test)]217mod tests {218    use super::super::BUDGET;219    use super::*;220221    fn read(code: u128, dimension: usize, measure: Measure, axis: Axis, count: usize) -> Vec<i128> {222        terms(&Key::new(code, dimension, 2, measure, axis), count, BUDGET)223            .unwrap()224            .0225    }226227    #[test]228    fn the_closed_measures_read_the_classics() {229        assert_eq!(read(7, 2, Measure::Voids, Axis::Level, 3), [1, 17, 217]);230        assert_eq!(231            read(7, 2, Measure::Surface, Axis::Level, 4),232            [16, 80, 496, 3536]233        );234        assert_eq!(235            read(23, 3, Measure::Fills, Axis::Side, 4),236            [20, 81, 208, 425]237        );238        assert_eq!(239            read(23, 3, Measure::Voids, Axis::Side, 4),240            [7, 44, 135, 304]241        );242        assert_eq!(read(1, 2, Measure::Fills, Axis::Side, 3), [4, 9, 16]);243        assert_eq!(read(9, 2, Measure::Fills, Axis::Side, 3), [5, 13, 25]);244        assert_eq!(read(11, 2, Measure::Fills, Axis::Side, 3), [7, 19, 37]);245        assert_eq!(read(15, 2, Measure::Fills, Axis::Side, 3), [9, 25, 49]);246        assert_eq!(read(129, 3, Measure::Fills, Axis::Side, 3), [9, 35, 91]);247        assert_eq!(read(255, 3, Measure::Fills, Axis::Side, 3), [27, 125, 343]);248    }249250    #[test]251    fn the_grid_measures_agree_with_the_censuses() {252        assert_eq!(read(7, 2, Measure::Euler, Axis::Level, 3), [0, -8, -72]);253        assert_eq!(read(23, 3, Measure::Euler, Axis::Level, 2), [-4, -80]);254        assert_eq!(read(23, 3, Measure::Faces, Axis::Level, 1), [96]);255        assert_eq!(read(23, 3, Measure::Vertices, Axis::Level, 1), [64]);256        assert_eq!(read(23, 3, Measure::Edges, Axis::Level, 1), [144]);257        assert_eq!(read(7, 2, Measure::Vertices, Axis::Side, 2), [16, 36]);258        assert_eq!(read(23, 3, Measure::Triangles, Axis::Level, 2), [42, 306]);259        assert_eq!(read(23, 3, Measure::Pieces, Axis::Side, 2), [1, 7]);260        assert_eq!(read(255, 3, Measure::Holes, Axis::Level, 2), [0, 0]);261        assert_eq!(read(23, 3, Measure::Peak, Axis::Level, 2), [6, 42]);262        assert_eq!(read(23, 3, Measure::Heights, Axis::Level, 2), [7, 25]);263    }264265    #[test]266    fn the_budget_caps_honestly() {267        let key = Key::new(23, 3, 2, Measure::Euler, Axis::Level);268        assert_eq!(terms(&key, 8, 1000).unwrap(), (vec![-4, -80], true));269        assert_eq!(terms(&key, 8, 26).unwrap(), (vec![], true));270        let deep = Key::new(23, 3, 2, Measure::Fills, Axis::Level);271        let (fills, capped) = terms(&deep, 40, BUDGET).unwrap();272        assert!(capped && fills.len() == 29);273        let base3 = Key::new(100, 2, 3, Measure::Surface, Axis::Side);274        assert_eq!(terms(&base3, 2, BUDGET).unwrap().0.len(), 2);275        assert!(terms(&Key::new(7, 2, 2, Measure::Faces, Axis::Level), 1, BUDGET).is_err());276        assert!(terms(&Key::new(16, 2, 2, Measure::Fills, Axis::Level), 1, BUDGET).is_err());277    }278279    #[test]280    fn the_closed_forms_match_the_terms() {281        for code in 0..16u128 {282            for measure in [Measure::Fills, Measure::Voids] {283                let key = Key::new(code, 2, 2, measure, Axis::Side);284                let Some(Closed::Polynomial(poly)) = closed(&key).unwrap() else {285                    panic!("no polynomial");286                };287                let (terms, _) = terms(&key, 5, BUDGET).unwrap();288                for (index, &term) in terms.iter().enumerate() {289                    let k = index as i128 + 2;290                    let value: i128 = poly291                        .iter()292                        .enumerate()293                        .map(|(p, &c)| c * k.pow(p as u32))294                        .sum();295                    assert_eq!(value, term, "code={code} k={k}");296                }297            }298        }299        assert_eq!(300            closed(&Key::new(23, 3, 2, Measure::Fills, Axis::Level)).unwrap(),301            Some(Closed::Power(20))302        );303        assert_eq!(304            closed(&Key::new(7, 2, 2, Measure::Voids, Axis::Level)).unwrap(),305            Some(Closed::Difference(9, 8))306        );307        assert_eq!(308            closed(&Key::new(7, 2, 3, Measure::Fills, Axis::Side)).unwrap(),309            None310        );311        assert_eq!(312            closed(&Key::new(7, 2, 2, Measure::Euler, Axis::Level)).unwrap(),313            None314        );315    }316}