terms.rs
12.2 kB · rust · 332 lines
1use super::{Axis, Closed, Key, Measure};2use mrlyrs::core::error::{value_error, Result};3use mrlyrs::core::tensor::Tensor;4use mrlyrs::math::bang::{code_to_corners, factory, Code};5use mrlyrs::math::counts;6use mrlyrs::math::{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(Code::from(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(31 Code::from(code),32 number,33 level as usize,34 0,35 base,36 )?)?;37 match measure {38 Measure::Vertices => tally.vertices as i128,39 Measure::Edges => tally.edges as i128,40 Measure::Euler => tally.euler as i128,41 _ => return value_error("the plane carries no faces."),42 }43 }44 _ => {45 let tally = three::census(&three::create(46 Code::from(code),47 number,48 level as usize,49 base,50 )?)?;51 match measure {52 Measure::Vertices => tally.vertices as i128,53 Measure::Edges => tally.edges as i128,54 Measure::Faces => tally.faces as i128,55 _ => tally.euler as i128,56 }57 }58 };59 Ok(value)60}6162fn term(key: &Key, number: usize, level: u32, cells: u128) -> Result<Option<i128>> {63 let Key {64 code,65 dimension,66 base,67 measure,68 ..69 } = *key;70 let fill = || -> Result<Option<u128>> {71 Ok(counts::fill(Code::from(code), number, dimension, 1, base)?.checked_pow(level))72 };73 let within = |cost: Option<u128>| cost.is_some_and(|cost| cost <= cells);74 let value = match measure {75 Measure::Fills => fill()?.and_then(|fill| i128::try_from(fill).ok()),76 Measure::Voids => grid(number, dimension, level)77 .zip(fill()?)78 .map(|(grid, fill)| (grid - fill) as i128),79 Measure::Surface => {80 let corners = code_to_corners(Code::from(code), dimension, base)?;81 counts::Exposure::from_corners(&corners, number, dimension, base)82 .at(level)83 .map(|faces| faces as i128)84 }85 Measure::Peak | Measure::Heights => {86 let span = (number as u128)87 .checked_pow(level)88 .map(|side| dimension as u128 * (side - 1) + 1);89 if !within(span) {90 return Ok(None);91 }92 let counts = counts::profile_of_tile(&tile(key, number)?, level)?;93 Some(if measure == Measure::Peak {94 counts.iter().max().copied().unwrap_or(0) as i12895 } else {96 counts.iter().filter(|&&count| count > 0).count() as i12897 })98 }99 Measure::Vertices | Measure::Edges | Measure::Faces | Measure::Euler => {100 if !within(grid(number, dimension, level)) {101 return Ok(None);102 }103 Some(complex(key, number, level)?)104 }105 Measure::Triangles => {106 let cost = (number as u128)107 .checked_pow(2 * level)108 .and_then(|side| side.checked_mul(16));109 if !within(cost) {110 return Ok(None);111 }112 Some(counts::cut_fills(Code::from(code), number, level)? as i128)113 }114 Measure::Holes | Measure::Pieces => {115 if !within(grid(number, dimension, level)) {116 return Ok(None);117 }118 let slice = six::cut(&three::create(119 Code::from(code),120 number,121 level as usize,122 base,123 )?)?;124 Some(if measure == Measure::Holes {125 six::holes(&slice)? as i128126 } else {127 six::components(&slice)? as i128128 })129 }130 };131 Ok(value)132}133134/// Reads the first terms of a design sequence within a cell budget, and whether the budget or a u128 cut them short.135///136/// ```137/// use ledger::{terms, Axis, Key, Measure, BUDGET};138/// let carpet = Key::new(7, 2, 2, Measure::Fills, Axis::Level);139/// assert_eq!(terms(&carpet, 3, BUDGET).unwrap(), (vec![8, 64, 512], false));140/// let sponge = Key::new(23, 3, 2, Measure::Surface, Axis::Level);141/// assert_eq!(terms(&sponge, 3, BUDGET).unwrap(), (vec![72, 1056, 18048], false));142/// ```143pub fn terms(key: &Key, count: usize, cells: u128) -> Result<(Vec<i128>, bool)> {144 code_to_corners(Code::from(key.code), key.dimension, key.base)?;145 if !key.measure.applies(key.dimension, key.base) {146 return value_error(format!(147 "{} does not read dimension {} base {}.",148 key.measure.slug(),149 key.dimension,150 key.base151 ));152 }153 let mut out = Vec::with_capacity(count);154 for index in 0..count {155 let (number, level) = key.axis.place(index, key.number());156 match term(key, number, level, cells)? {157 Some(value) => out.push(value),158 None => return Ok((out, true)),159 }160 }161 Ok((out, false))162}163164/// 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`.165///166/// ```167/// let carpet = mrlyrs::math::bang::code_to_corners(mrlyrs::math::bang::Code::from(7u64), 2, 2).unwrap();168/// assert_eq!(ledger::fill_polynomial(&carpet, 2), [0, -2, 3]);169/// ```170pub fn fill_polynomial(corners: &[Vec<u8>], dimension: usize) -> Vec<i128> {171 let mut out = vec![0i128; dimension + 1];172 for corner in corners {173 let ones = corner.iter().filter(|&&r| r != 0).count();174 for j in 0..=ones {175 let sign = if (ones - j).is_multiple_of(2) { 1 } else { -1 };176 out[dimension - ones + j] += sign * binomial(ones, j);177 }178 }179 out180}181182fn odd_power(dimension: usize) -> Vec<i128> {183 (0..=dimension)184 .map(|j| {185 let sign = if (dimension - j).is_multiple_of(2) {186 1187 } else {188 -1189 };190 sign * binomial(dimension, j) * (1i128 << j)191 })192 .collect()193}194195/// Returns the closed form of a design sequence when the ledger knows one.196///197/// Level fills are a power and level voids a difference of powers; base-2 side fills and voids198/// are polynomials in `k` at side `2k - 1`; the level surface obeys the exposure recurrence.199pub fn closed(key: &Key) -> Result<Option<Closed>> {200 let Key {201 code,202 dimension,203 base,204 measure,205 axis,206 } = *key;207 let corners = code_to_corners(Code::from(code), dimension, base)?;208 let fill = || counts::fill(Code::from(code), key.number(), dimension, 1, base);209 let form = match (measure, axis) {210 (Measure::Fills, Axis::Level) => Closed::Power(fill()?),211 (Measure::Voids, Axis::Level) => {212 Closed::Difference((key.number() as u128).pow(dimension as u32), fill()?)213 }214 (Measure::Fills, Axis::Side) if base == 2 => {215 Closed::Polynomial(fill_polynomial(&corners, dimension))216 }217 (Measure::Voids, Axis::Side) if base == 2 => Closed::Polynomial(218 odd_power(dimension)219 .iter()220 .zip(fill_polynomial(&corners, dimension))221 .map(|(all, filled)| all - filled)222 .collect(),223 ),224 (Measure::Surface, Axis::Level) => Closed::Recurrence(225 counts::Exposure::from_corners(&corners, key.number(), dimension, base).recurrence(),226 ),227 _ => return Ok(None),228 };229 Ok(Some(form))230}231232#[cfg(test)]233mod tests {234 use super::super::BUDGET;235 use super::*;236237 fn read(code: u128, dimension: usize, measure: Measure, axis: Axis, count: usize) -> Vec<i128> {238 terms(&Key::new(code, dimension, 2, measure, axis), count, BUDGET)239 .unwrap()240 .0241 }242243 #[test]244 fn the_closed_measures_read_the_classics() {245 assert_eq!(read(7, 2, Measure::Voids, Axis::Level, 3), [1, 17, 217]);246 assert_eq!(247 read(7, 2, Measure::Surface, Axis::Level, 4),248 [16, 80, 496, 3536]249 );250 assert_eq!(251 read(23, 3, Measure::Fills, Axis::Side, 4),252 [20, 81, 208, 425]253 );254 assert_eq!(255 read(23, 3, Measure::Voids, Axis::Side, 4),256 [7, 44, 135, 304]257 );258 assert_eq!(read(1, 2, Measure::Fills, Axis::Side, 3), [4, 9, 16]);259 assert_eq!(read(9, 2, Measure::Fills, Axis::Side, 3), [5, 13, 25]);260 assert_eq!(read(11, 2, Measure::Fills, Axis::Side, 3), [7, 19, 37]);261 assert_eq!(read(15, 2, Measure::Fills, Axis::Side, 3), [9, 25, 49]);262 assert_eq!(read(129, 3, Measure::Fills, Axis::Side, 3), [9, 35, 91]);263 assert_eq!(read(255, 3, Measure::Fills, Axis::Side, 3), [27, 125, 343]);264 }265266 #[test]267 fn the_grid_measures_agree_with_the_censuses() {268 assert_eq!(read(7, 2, Measure::Euler, Axis::Level, 3), [0, -8, -72]);269 assert_eq!(read(23, 3, Measure::Euler, Axis::Level, 2), [-4, -80]);270 assert_eq!(read(23, 3, Measure::Faces, Axis::Level, 1), [96]);271 assert_eq!(read(23, 3, Measure::Vertices, Axis::Level, 1), [64]);272 assert_eq!(read(23, 3, Measure::Edges, Axis::Level, 1), [144]);273 assert_eq!(read(7, 2, Measure::Vertices, Axis::Side, 2), [16, 36]);274 assert_eq!(read(23, 3, Measure::Triangles, Axis::Level, 2), [42, 306]);275 assert_eq!(read(23, 3, Measure::Pieces, Axis::Side, 2), [1, 7]);276 assert_eq!(read(255, 3, Measure::Holes, Axis::Level, 2), [0, 0]);277 assert_eq!(read(23, 3, Measure::Peak, Axis::Level, 2), [6, 42]);278 assert_eq!(read(23, 3, Measure::Heights, Axis::Level, 2), [7, 25]);279 }280281 #[test]282 fn the_budget_caps_honestly() {283 let key = Key::new(23, 3, 2, Measure::Euler, Axis::Level);284 assert_eq!(terms(&key, 8, 1000).unwrap(), (vec![-4, -80], true));285 assert_eq!(terms(&key, 8, 26).unwrap(), (vec![], true));286 let deep = Key::new(23, 3, 2, Measure::Fills, Axis::Level);287 let (fills, capped) = terms(&deep, 40, BUDGET).unwrap();288 assert!(capped && fills.len() == 29);289 let base3 = Key::new(100, 2, 3, Measure::Surface, Axis::Side);290 assert_eq!(terms(&base3, 2, BUDGET).unwrap().0.len(), 2);291 assert!(terms(&Key::new(7, 2, 2, Measure::Faces, Axis::Level), 1, BUDGET).is_err());292 assert!(terms(&Key::new(16, 2, 2, Measure::Fills, Axis::Level), 1, BUDGET).is_err());293 }294295 #[test]296 fn the_closed_forms_match_the_terms() {297 for code in 0..16u128 {298 for measure in [Measure::Fills, Measure::Voids] {299 let key = Key::new(code, 2, 2, measure, Axis::Side);300 let Some(Closed::Polynomial(poly)) = closed(&key).unwrap() else {301 panic!("no polynomial");302 };303 let (terms, _) = terms(&key, 5, BUDGET).unwrap();304 for (index, &term) in terms.iter().enumerate() {305 let k = index as i128 + 2;306 let value: i128 = poly307 .iter()308 .enumerate()309 .map(|(p, &c)| c * k.pow(p as u32))310 .sum();311 assert_eq!(value, term, "code={code} k={k}");312 }313 }314 }315 assert_eq!(316 closed(&Key::new(23, 3, 2, Measure::Fills, Axis::Level)).unwrap(),317 Some(Closed::Power(20))318 );319 assert_eq!(320 closed(&Key::new(7, 2, 2, Measure::Voids, Axis::Level)).unwrap(),321 Some(Closed::Difference(9, 8))322 );323 assert_eq!(324 closed(&Key::new(7, 2, 3, Measure::Fills, Axis::Side)).unwrap(),325 None326 );327 assert_eq!(328 closed(&Key::new(7, 2, 2, Measure::Euler, Axis::Level)).unwrap(),329 None330 );331 }332}