sieve.rs

6.7 kB · rust · 192 lines

1use crate::space::Pack;2use crate::{Fault, Grid};3use mrlyrs::core::json;4use mrlyrs::math::three::Vec3;5use mrlyrs::num::sieve;6use wasm_bindgen::prelude::*;78const PLANE_SITES: usize = 4_000_000;9const HOLE_BUDGET: u128 = 20_000;10const READ_LEVELS: usize = 16;11const WALK_STOPS: usize = 400;1213fn word(kind: &str, letter: u32, levels: usize) -> Result<Vec<u64>, Fault> {14    let side = u64::from(letter);15    if levels > READ_LEVELS {16        return Err(Fault::new(format!(17            "the levels must be between 1 and {READ_LEVELS}."18        )));19    }20    match kind {21        "odd" => Ok(sieve::odd_word(levels)),22        "flat" => {23            if side < 3 || side % 2 == 0 || side > 15 {24                return Err(Fault::new(format!(25                    "the letter {letter} is not an odd side between three and fifteen."26                )));27            }28            Ok(sieve::flat_word(side, levels))29        }30        _ => Err(Fault::new(format!("no schedule is named {kind:?}."))),31    }32}3334fn axes(dimension: usize) -> Result<u32, Fault> {35    match dimension {36        2 | 3 => Ok(dimension as u32),37        _ => Err(Fault::new("the sieve draws in two or three dimensions.")),38    }39}4041fn fits(word: &[u64], dimension: u32) -> Result<bool, Fault> {42    if dimension == 2 {43        let side = sieve::side(word)?;44        return Ok(side * side <= PLANE_SITES as u128);45    }46    Ok(sieve::holes(word, dimension)? <= HOLE_BUDGET)47}4849/// 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.50#[wasm_bindgen]51pub fn wallis_cap(kind: &str, letter: u32, dimension: usize) -> Result<usize, Fault> {52    let axes = axes(dimension)?;53    let mut top = 1;54    for levels in 1..=READ_LEVELS {55        if !fits(&word(kind, letter, levels)?, axes)? {56            break;57        }58        top = levels;59    }60    Ok(top)61}6263/// 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.64#[wasm_bindgen]65pub fn wallis_read(66    kind: &str,67    letter: u32,68    levels: usize,69    dimension: usize,70) -> Result<String, Fault> {71    let axes = axes(dimension)?;72    let schedule = word(kind, letter, levels.max(2))?;73    let word = word(kind, letter, levels)?;74    let mut rows: Vec<mrlyrs::core::Json> = Vec::with_capacity(word.len());75    for n in 1..=word.len() {76        let prefix = &word[..n];77        rows.push(json!({78            "level": n,79            "letter": prefix[n - 1],80            "side": sieve::side(prefix)?.to_string(),81            "cells": sieve::cells(prefix, axes)?.to_string(),82            "holes": sieve::holes(prefix, axes)?.to_string(),83            "ratio": sieve::ratio(prefix, axes)?,84            "exponent": sieve::exponent(prefix, axes)?,85        }));86    }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    let mut out = Vec::with_capacity(stops);124    for n in 1..=stops {125        out.push(sieve::ratio(&word[..n], axes)?);126    }127    Ok(out)128}129130/// 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.131#[wasm_bindgen]132pub fn wallis_grid(kind: &str, letter: u32, levels: usize) -> Result<Grid, Fault> {133    let word = word(kind, letter, levels)?;134    if !fits(&word, 2)? {135        return Err(Fault::new(format!(136            "a side of {} is more than this page rasters; lower the level.",137            sieve::side(&word)?138        )));139    }140    let (side, sites) = sieve::raster(&word)?;141    Ok(Grid {142        width: side as u32,143        height: side as u32,144        types: sites,145    })146}147148/// 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.149#[wasm_bindgen]150pub fn wallis_faces(kind: &str, letter: u32, levels: usize) -> Result<Vec<f32>, Fault> {151    let word = word(kind, letter, levels)?;152    if !fits(&word, 3)? {153        return Err(Fault::new(format!(154            "{} punctures is more than this page draws; lower the level.",155            sieve::holes(&word, 3)?156        )));157    }158    let half = sieve::side(&word)? as f32 / 2.0;159    let at =160        |x: f32, y: f32, z: f32| Vec3::new((x - half) / half, (y - half) / half, (z - half) / half);161    let mut pack = Pack::new();162    for hole in sieve::punctures(&word, 3)?.chunks(4) {163        let (x, y, z) = (hole[0] as f32, hole[1] as f32, hole[2] as f32);164        let s = hole[3] as f32;165        let (a, b, c) = (x + s, y + s, z + s);166        pack.quad(167            [at(x, y, z), at(x, y, c), at(x, b, c), at(x, b, z)],168            Vec3::new(-1.0, 0.0, 0.0),169        );170        pack.quad(171            [at(a, y, z), at(a, b, z), at(a, b, c), at(a, y, c)],172            Vec3::new(1.0, 0.0, 0.0),173        );174        pack.quad(175            [at(x, y, z), at(a, y, z), at(a, y, c), at(x, y, c)],176            Vec3::new(0.0, -1.0, 0.0),177        );178        pack.quad(179            [at(x, b, z), at(x, b, c), at(a, b, c), at(a, b, z)],180            Vec3::new(0.0, 1.0, 0.0),181        );182        pack.quad(183            [at(x, y, z), at(x, b, z), at(a, b, z), at(a, y, z)],184            Vec3::new(0.0, 0.0, -1.0),185        );186        pack.quad(187            [at(x, y, c), at(a, y, c), at(a, b, c), at(x, b, c)],188            Vec3::new(0.0, 0.0, 1.0),189        );190    }191    Ok(pack.buffer())192}