bang.rs

7.2 kB · rust · 228 lines

1use crate::{checked, code_of, Fault};2use mrlyrs::core::{json, Json, Rng};3use mrlyrs::math::bang::Code;4use mrlyrs::math::bang::{self, baseq, code_to_corners};5use mrlyrs::math::counts;6use mrlyrs::math::name::{Bang, Named};7use wasm_bindgen::prelude::*;89fn strings(values: Vec<u128>) -> Vec<String> {10    values.iter().map(|v| v.to_string()).collect()11}1213fn stream(seed: u32, lane: u64) -> Rng {14    Rng::new(u64::from(seed) | (lane << 32))15}1617fn draw(rng: &mut Rng, dimension: usize, base: usize) -> Result<u128, Fault> {18    let cells = base19        .checked_pow(dimension as u32)20        .filter(|&cells| cells < 128)21        .ok_or_else(|| Fault::new("too many corners to draw a code."))?;22    loop {23        let code = (0..cells).fold(0u128, |code, bit| code | (u128::from(rng.boolean()) << bit));24        if code.count_ones() >= 2 && code_to_corners(Code::from(code), dimension, base).is_ok() {25            return Ok(code);26        }27    }28}2930/// Enumerates a universe of dimension one to three: its code total, its distinct count and every canonical design with its orbit size, name, degree, normal form and corners, as JSON.31#[wasm_bindgen]32pub fn universe(dimension: usize) -> Result<String, Fault> {33    if !(1..=3).contains(&dimension) {34        return Err(Fault::new(35            "the universe gallery runs from dimension 1 to 3.",36        ));37    }38    let universe = bang::bang(dimension)?;39    let designs: Vec<Json> = universe40        .canonical()41        .iter()42        .map(|design| {43            json!({44                "code": design.i.to_string(),45                "orbit": design.orbit_size,46                "name": design.name().unwrap_or_default(),47                "degree": design.degree(),48                "anf": design.anf(),49                "corners": design.rule(),50            })51        })52        .collect();53    Ok(json!({54        "dimension": dimension,55        "total": universe.total,56        "distinct": universe.distinct(),57        "designs": designs,58    })59    .to_string())60}6162/// Counts the designs distinct under symmetry for dimensions one through the limit, each as a decimal string.63#[wasm_bindgen]64pub fn counting_sequence(max_dimension: usize) -> Result<Vec<String>, Fault> {65    Ok(strings(baseq::sequence(2, max_dimension)?))66}6768/// Counts the base-q designs distinct under symmetry for dimensions one through the limit, each as a decimal string.69#[wasm_bindgen]70pub fn baseq_sequence(base: usize, max_dimension: usize) -> Result<Vec<String>, Fault> {71    Ok(strings(baseq::sequence(base, max_dimension)?))72}7374/// Counts the fill classes, the popcount profiles of the base-2 designs, for dimensions one through the limit, each as a decimal string.75#[wasm_bindgen]76pub fn classes_sequence(max_dimension: usize) -> Vec<String> {77    strings(baseq::class_sequence(max_dimension))78}7980/// Counts the filled sites of the code's fractal at the level in closed form, as a decimal string.81#[wasm_bindgen]82pub fn fills(83    code: &str,84    number: usize,85    dimension: usize,86    level: u32,87    base: usize,88) -> Result<String, Fault> {89    Ok(counts::fill(Code::from(code_of(code)?), number, dimension, level, base)?.to_string())90}9192/// Counts the empty sites of the code's fractal at the level in closed form, as a decimal string.93#[wasm_bindgen]94pub fn voids(95    code: &str,96    number: usize,97    dimension: usize,98    level: u32,99    base: usize,100) -> Result<String, Fault> {101    Ok(counts::void(Code::from(code_of(code)?), number, dimension, level, base)?.to_string())102}103104/// Returns the filled fraction of the code's fractal at the level.105#[wasm_bindgen]106pub fn ratio(107    code: &str,108    number: usize,109    dimension: usize,110    level: u32,111    base: usize,112) -> Result<f64, Fault> {113    Ok(counts::ratio(114        Code::from(code_of(code)?),115        number,116        dimension,117        level,118        base,119    )?)120}121122/// Returns the code's fractal dimension at the side number.123#[wasm_bindgen]124pub fn dimension(125    code: &str,126    number: usize,127    base_dimension: usize,128    base: usize,129) -> Result<f64, Fault> {130    Ok(counts::dimension(131        Code::from(code_of(code)?),132        number,133        base_dimension,134        base,135    )?)136}137138/// Prints the name of a design code at its dimension and base as a line of prose.139#[wasm_bindgen]140pub fn name_of(code: &str, dimension: usize, base: usize) -> Result<String, Fault> {141    Ok(Bang::new(checked(code, dimension, base)?, dimension, base).to_mrly()?)142}143144/// Reads a design's file name back into its code, dim and base, as JSON.145#[wasm_bindgen]146pub fn name_parse(text: &str) -> Result<String, Fault> {147    let bang = Bang::from_file(text)?;148    Ok(json!({149        "code": bang.code.to_string(),150        "dim": bang.dim,151        "base": bang.base,152    })153    .to_string())154}155156/// Draws one design code of the dimension and base from the seed, uniform over the codes that fill a corner.157#[wasm_bindgen]158pub fn random_code(dimension: usize, base: usize, seed: u32) -> Result<String, Fault> {159    Ok(draw(&mut stream(seed, 0), dimension, base)?.to_string())160}161162/// Draws a run of design codes of the dimension and base from the seed, each uniform over the codes that fill a corner.163#[wasm_bindgen]164pub fn random_codes(165    dimension: usize,166    base: usize,167    seed: u32,168    count: usize,169) -> Result<Vec<String>, Fault> {170    let mut rng = stream(seed, 0);171    (0..count)172        .map(|_| Ok(draw(&mut rng, dimension, base)?.to_string()))173        .collect()174}175176/// Draws one whole number between each low and high inclusive from the seed's second lane, so a page's extra draws never echo its code.177#[wasm_bindgen]178pub fn random_between(seed: u32, lows: &[i32], highs: &[i32]) -> Vec<i32> {179    let mut rng = stream(seed, 1);180    lows.iter()181        .zip(highs)182        .map(|(&low, &high)| rng.range(i64::from(low), i64::from(high)) as i32)183        .collect()184}185186/// Returns the largest level, at least one, at which a grid of the number and dimension holds at most the budget of cells.187#[wasm_bindgen]188pub fn level_cap(number: usize, dimension: usize, budget: usize) -> usize {189    if number < 2 {190        return 1;191    }192    let cells = |level: u32| (number as u128).checked_pow(dimension as u32 * level);193    let mut level = 1;194    while cells(level + 1).is_some_and(|count| count <= budget as u128) {195        level += 1;196    }197    level as usize198}199200/// Returns the largest level, at least one, at which the code's fractal fills at most the budget of sites.201#[wasm_bindgen]202pub fn fill_cap(203    code: &str,204    number: usize,205    dimension: usize,206    base: usize,207    budget: usize,208) -> Result<usize, Fault> {209    let code = checked(code, dimension, base)?;210    let fits = |level: u32| {211        counts::fill(Code::from(code), number, dimension, level, base)212            .is_ok_and(|count| count <= budget as u128)213    };214    let mut level = 1;215    while level < 40 && fits(level + 1) {216        level += 1;217    }218    Ok(level as usize)219}220221/// Counts the cells of a grid of the number and dimension at the level, as a decimal string.222#[wasm_bindgen]223pub fn grid_total(number: usize, dimension: usize, level: usize) -> Result<String, Fault> {224    (number as u128)225        .checked_pow((dimension * level) as u32)226        .map(|total| total.to_string())227        .ok_or_else(|| Fault::new("that grid holds more cells than a u128 counts."))228}