sieve.rs
6.6 kB · rust · 190 lines
1use crate::{Fault, Grid};2use mrlycore::json;3use mrlymath::space::{Pack, Vec3};4use mrlynum::sieve;5use wasm_bindgen::prelude::*;67const PLANE_SITES: usize = 4_000_000;8const HOLE_BUDGET: u128 = 20_000;9const READ_LEVELS: usize = 16;10const WALK_STOPS: usize = 400;1112fn word(kind: &str, letter: u32, levels: usize) -> Result<Vec<u64>, Fault> {13 let side = u64::from(letter);14 if levels > READ_LEVELS {15 return Err(Fault::new(format!(16 "the levels must be between 1 and {READ_LEVELS}."17 )));18 }19 match kind {20 "odd" => Ok(sieve::odd_word(levels)),21 "flat" => {22 if side < 3 || side % 2 == 0 || side > 15 {23 return Err(Fault::new(format!(24 "the letter {letter} is not an odd side between three and fifteen."25 )));26 }27 Ok(sieve::flat_word(side, levels))28 }29 _ => Err(Fault::new(format!("no schedule is named {kind:?}."))),30 }31}3233fn axes(dimension: usize) -> Result<u32, Fault> {34 match dimension {35 2 | 3 => Ok(dimension as u32),36 _ => Err(Fault::new("the sieve draws in two or three dimensions.")),37 }38}3940fn fits(word: &[u64], dimension: u32) -> bool {41 if dimension == 2 {42 let side = sieve::side(word);43 return side * side <= PLANE_SITES as u128;44 }45 sieve::holes(word, dimension) <= HOLE_BUDGET46}4748/// Returns the deepest level the schedule reaches before it outgrows the sites this page rasters in the plane or the punctures it draws in the cube.49#[wasm_bindgen]50pub fn wallis_cap(kind: &str, letter: u32, dimension: usize) -> Result<usize, Fault> {51 let axes = axes(dimension)?;52 let mut top = 1;53 for levels in 1..=READ_LEVELS {54 if !fits(&word(kind, letter, levels)?, axes) {55 break;56 }57 top = levels;58 }59 Ok(top)60}6162/// Reads the schedule at every level up to the one asked: its letter, its side, its surviving cells, its punctures, the share of the whole it leaves and the box exponent it reads, then the word's own reading beside the limit its schedule walks to and the gap left, as JSON.63#[wasm_bindgen]64pub fn wallis_read(65 kind: &str,66 letter: u32,67 levels: usize,68 dimension: usize,69) -> Result<String, Fault> {70 let axes = axes(dimension)?;71 let schedule = word(kind, letter, levels.max(2))?;72 let word = word(kind, letter, levels)?;73 let rows: Vec<mrlycore::Json> = (1..=word.len())74 .map(|n| {75 let prefix = &word[..n];76 json!({77 "level": n,78 "letter": prefix[n - 1],79 "side": sieve::side(prefix).to_string(),80 "cells": sieve::cells(prefix, axes).to_string(),81 "holes": sieve::holes(prefix, axes).to_string(),82 "ratio": sieve::ratio(prefix, axes),83 "exponent": sieve::exponent(prefix, axes),84 })85 })86 .collect();87 let ratio = sieve::ratio(&word, axes);88 let limit = sieve::limit(&schedule, axes).unwrap_or(0.0);89 Ok(json!({90 "word": word.clone(),91 "dimension": dimension,92 "side": sieve::side(&word).to_string(),93 "cells": sieve::cells(&word, axes).to_string(),94 "holes": sieve::holes(&word, axes).to_string(),95 "ratio": ratio,96 "exponent": sieve::exponent(&word, axes),97 "limit": limit,98 "gap": ratio - limit,99 "closed": limit > 0.0,100 "levels": rows,101 })102 .to_string())103}104105/// Walks the share of the whole the schedule leaves at level one through the count of stops, one number a level, so the approach to the limit can be drawn past the level the page rasters.106#[wasm_bindgen]107pub fn wallis_walk(108 kind: &str,109 letter: u32,110 dimension: usize,111 stops: usize,112) -> Result<Vec<f64>, Fault> {113 let axes = axes(dimension)?;114 if !(1..=WALK_STOPS).contains(&stops) {115 return Err(Fault::new(format!(116 "the stops must be between 1 and {WALK_STOPS}."117 )));118 }119 let word = match kind {120 "odd" => sieve::odd_word(stops),121 _ => word(kind, letter, 1).map(|_| sieve::flat_word(u64::from(letter), stops))?,122 };123 Ok((1..=stops)124 .map(|n| sieve::ratio(&word[..n], axes))125 .collect())126}127128/// Builds the plane sieve the schedule spells as a byte grid, one byte a site, one where the site survives and zero where a level punched it out.129#[wasm_bindgen]130pub fn wallis_grid(kind: &str, letter: u32, levels: usize) -> Result<Grid, Fault> {131 let word = word(kind, letter, levels)?;132 if !fits(&word, 2) {133 return Err(Fault::new(format!(134 "a side of {} is more than this page rasters; lower the level.",135 sieve::side(&word)136 )));137 }138 let (side, sites) = sieve::raster(&word);139 Ok(Grid {140 width: side as u32,141 height: side as u32,142 types: sites,143 })144}145146/// Packs the punctures of the solid sieve the schedule spells as boxes: two section lengths, then six floats per vertex, position and normal, in the unit box, one box a hole at the size the level that punched it left.147#[wasm_bindgen]148pub fn wallis_faces(kind: &str, letter: u32, levels: usize) -> Result<Vec<f32>, Fault> {149 let word = word(kind, letter, levels)?;150 if !fits(&word, 3) {151 return Err(Fault::new(format!(152 "{} punctures is more than this page draws; lower the level.",153 sieve::holes(&word, 3)154 )));155 }156 let half = sieve::side(&word) as f32 / 2.0;157 let at =158 |x: f32, y: f32, z: f32| Vec3::new((x - half) / half, (y - half) / half, (z - half) / half);159 let mut pack = Pack::new();160 for hole in sieve::punctures(&word, 3).chunks(4) {161 let (x, y, z) = (hole[0] as f32, hole[1] as f32, hole[2] as f32);162 let s = hole[3] as f32;163 let (a, b, c) = (x + s, y + s, z + s);164 pack.quad(165 [at(x, y, z), at(x, y, c), at(x, b, c), at(x, b, z)],166 Vec3::new(-1.0, 0.0, 0.0),167 );168 pack.quad(169 [at(a, y, z), at(a, b, z), at(a, b, c), at(a, y, c)],170 Vec3::new(1.0, 0.0, 0.0),171 );172 pack.quad(173 [at(x, y, z), at(a, y, z), at(a, y, c), at(x, y, c)],174 Vec3::new(0.0, -1.0, 0.0),175 );176 pack.quad(177 [at(x, b, z), at(x, b, c), at(a, b, c), at(a, b, z)],178 Vec3::new(0.0, 1.0, 0.0),179 );180 pack.quad(181 [at(x, y, z), at(x, b, z), at(a, b, z), at(a, y, z)],182 Vec3::new(0.0, 0.0, -1.0),183 );184 pack.quad(185 [at(x, y, c), at(a, y, c), at(a, b, c), at(x, b, c)],186 Vec3::new(0.0, 0.0, 1.0),187 );188 }189 Ok(pack.buffer())190}