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}