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}