bang.rs

7.1 kB · rust · 227 lines

1use crate::{checked, code_of, Fault};2use mrlycore::{json, Json, Rng};3use mrlymath::bang::{self, baseq, code_to_corners, counting};4use mrlymath::formulas;5use mrlymath::name::{Bang, Named};6use wasm_bindgen::prelude::*;78fn strings(values: Vec<u128>) -> Vec<String> {9    values.iter().map(|v| v.to_string()).collect()10}1112fn stream(seed: u32, lane: u64) -> Rng {13    Rng::new(u64::from(seed) | (lane << 32))14}1516fn draw(rng: &mut Rng, dimension: usize, base: usize) -> Result<u128, Fault> {17    let cells = base18        .checked_pow(dimension as u32)19        .filter(|&cells| cells < 128)20        .ok_or_else(|| Fault::new("too many corners to draw a code."))?;21    loop {22        let code = (0..cells).fold(0u128, |code, bit| code | (u128::from(rng.boolean()) << bit));23        if code.count_ones() >= 2 && code_to_corners(code, dimension, base).is_ok() {24            return Ok(code);25        }26    }27}2829/// 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.30#[wasm_bindgen]31pub fn universe(dimension: usize) -> Result<String, Fault> {32    if !(1..=3).contains(&dimension) {33        return Err(Fault::new(34            "the universe gallery runs from dimension 1 to 3.",35        ));36    }37    let universe = bang::bang(dimension);38    let designs: Vec<Json> = universe39        .canonical()40        .iter()41        .map(|design| {42            json!({43                "code": design.i.to_string(),44                "orbit": design.orbit_size,45                "name": design.name(),46                "degree": design.degree(),47                "anf": design.anf(),48                "corners": design.rule(),49            })50        })51        .collect();52    Ok(json!({53        "dimension": dimension,54        "total": universe.total,55        "distinct": universe.distinct(),56        "designs": designs,57    })58    .to_string())59}6061/// Counts the designs distinct under symmetry for dimensions one through the limit, each as a decimal string.62#[wasm_bindgen]63pub fn counting_sequence(max_dimension: usize) -> Result<Vec<String>, Fault> {64    Ok(strings(counting::sequence(max_dimension)?))65}6667/// Counts the base-q designs distinct under symmetry for dimensions one through the limit, each as a decimal string.68#[wasm_bindgen]69pub fn baseq_sequence(base: usize, max_dimension: usize) -> Result<Vec<String>, Fault> {70    Ok(strings(baseq::sequence(base, max_dimension)?))71}7273/// Counts the fill classes, the popcount profiles of the base-2 designs, for dimensions one through the limit, each as a decimal string.74#[wasm_bindgen]75pub fn classes_sequence(max_dimension: usize) -> Vec<String> {76    strings(counting::class_sequence(max_dimension))77}7879/// Counts the filled sites of the code's fractal at the level in closed form, as a decimal string.80#[wasm_bindgen]81pub fn fills(82    code: &str,83    number: usize,84    dimension: usize,85    level: u32,86    base: usize,87) -> Result<String, Fault> {88    Ok(formulas::fill(code_of(code)?, number, dimension, level, base)?.to_string())89}9091/// Counts the empty sites of the code's fractal at the level in closed form, as a decimal string.92#[wasm_bindgen]93pub fn voids(94    code: &str,95    number: usize,96    dimension: usize,97    level: u32,98    base: usize,99) -> Result<String, Fault> {100    Ok(formulas::void(code_of(code)?, number, dimension, level, base)?.to_string())101}102103/// Returns the filled fraction of the code's fractal at the level.104#[wasm_bindgen]105pub fn ratio(106    code: &str,107    number: usize,108    dimension: usize,109    level: u32,110    base: usize,111) -> Result<f64, Fault> {112    Ok(formulas::ratio(113        code_of(code)?,114        number,115        dimension,116        level,117        base,118    )?)119}120121/// Returns the code's fractal dimension at the side number.122#[wasm_bindgen]123pub fn dimension(124    code: &str,125    number: usize,126    base_dimension: usize,127    base: usize,128) -> Result<f64, Fault> {129    Ok(formulas::dimension(130        code_of(code)?,131        number,132        base_dimension,133        base,134    )?)135}136137/// Prints the name of a design code at its dimension and base as a line of prose.138#[wasm_bindgen]139pub fn name_of(code: &str, dimension: usize, base: usize) -> Result<String, Fault> {140    Ok(Bang::new(checked(code, dimension, base)?, dimension, base).to_mrly())141}142143/// Reads a design's file name back into its code, dim and base, as JSON.144#[wasm_bindgen]145pub fn name_parse(text: &str) -> Result<String, Fault> {146    let bang = Bang::from_file(text)?;147    Ok(json!({148        "code": bang.code.to_string(),149        "dim": bang.dim,150        "base": bang.base,151    })152    .to_string())153}154155/// Draws one design code of the dimension and base from the seed, uniform over the codes that fill a corner.156#[wasm_bindgen]157pub fn random_code(dimension: usize, base: usize, seed: u32) -> Result<String, Fault> {158    Ok(draw(&mut stream(seed, 0), dimension, base)?.to_string())159}160161/// Draws a run of design codes of the dimension and base from the seed, each uniform over the codes that fill a corner.162#[wasm_bindgen]163pub fn random_codes(164    dimension: usize,165    base: usize,166    seed: u32,167    count: usize,168) -> Result<Vec<String>, Fault> {169    let mut rng = stream(seed, 0);170    (0..count)171        .map(|_| Ok(draw(&mut rng, dimension, base)?.to_string()))172        .collect()173}174175/// 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.176#[wasm_bindgen]177pub fn random_between(seed: u32, lows: &[i32], highs: &[i32]) -> Vec<i32> {178    let mut rng = stream(seed, 1);179    lows.iter()180        .zip(highs)181        .map(|(&low, &high)| rng.range(i64::from(low), i64::from(high)) as i32)182        .collect()183}184185/// Returns the largest level, at least one, at which a grid of the number and dimension holds at most the budget of cells.186#[wasm_bindgen]187pub fn level_cap(number: usize, dimension: usize, budget: usize) -> usize {188    if number < 2 {189        return 1;190    }191    let cells = |level: u32| (number as u128).checked_pow(dimension as u32 * level);192    let mut level = 1;193    while cells(level + 1).is_some_and(|count| count <= budget as u128) {194        level += 1;195    }196    level as usize197}198199/// Returns the largest level, at least one, at which the code's fractal fills at most the budget of sites.200#[wasm_bindgen]201pub fn fill_cap(202    code: &str,203    number: usize,204    dimension: usize,205    base: usize,206    budget: usize,207) -> Result<usize, Fault> {208    let code = checked(code, dimension, base)?;209    let fits = |level: u32| {210        formulas::fill(code, number, dimension, level, base)211            .is_ok_and(|count| count <= budget as u128)212    };213    let mut level = 1;214    while level < 40 && fits(level + 1) {215        level += 1;216    }217    Ok(level as usize)218}219220/// Counts the cells of a grid of the number and dimension at the level, as a decimal string.221#[wasm_bindgen]222pub fn grid_total(number: usize, dimension: usize, level: usize) -> Result<String, Fault> {223    (number as u128)224        .checked_pow((dimension * level) as u32)225        .map(|total| total.to_string())226        .ok_or_else(|| Fault::new("that grid holds more cells than a u128 counts."))227}