lattice.rs

4.8 kB · rust · 147 lines

1use crate::{rgba, theme, Fault, Pixels};2use mrlycore::{json, Json};3use mrlynum::factor::gcd;4use mrlynum::{lattice, series};5use wasm_bindgen::prelude::*;67/// Walks the Farey sequence of the order: each node's numerator, denominator and brightness, as JSON.8#[wasm_bindgen]9pub fn farey(order: usize) -> String {10    let nodes: Vec<Json> = lattice::farey(order)11        .iter()12        .map(|node| json!([node.num, node.den, node.brightness]))13        .collect();14    Json::Array(nodes).to_string()15}1617/// Sieves the totients of zero through the limit.18#[wasm_bindgen]19pub fn totients(limit: usize) -> Vec<u32> {20    lattice::totients(limit).iter().map(|&v| v as u32).collect()21}2223/// Reads the Farey stack of the order: the nodes the walk lit, one plus the totients summed, whether the two agree, and the primes as the scales of maximal novelty, as JSON.24#[wasm_bindgen]25pub fn farey_novelty(order: usize) -> String {26    let phi = lattice::totients(order);27    let novel = 1 + phi.iter().skip(1).sum::<u64>();28    let lit = lattice::farey(order).len() as u64;29    let primes: Vec<usize> = (2..=order).filter(|&n| phi[n] == n as u64 - 1).collect();30    json!({31        "lit": lit,32        "novel": novel,33        "match": lit == novel,34        "primes": primes,35    })36    .to_string()37}3839// VISIBLE4041const WINDOW: usize = 4000;42const SHEET: usize = 2048;43const STOPS: usize = 512;4445fn window(n: usize) -> Result<usize, Fault> {46    if n == 0 || n > WINDOW {47        return Err(Fault::new(format!(48            "the window must be between 1 and {WINDOW}."49        )));50    }51    Ok(n)52}5354fn depth(dimension: u32) -> Result<u32, Fault> {55    if !(2..=8).contains(&dimension) {56        return Err(Fault::new("the dimension must be between 2 and 8."));57    }58    Ok(dimension)59}6061fn shade(layer: usize, layers: bool) -> [u8; 4] {62    let ink = theme();63    if layer == 1 {64        return rgba(ink.blue);65    }66    if !layers {67        return rgba(ink.line);68    }69    let t = 1.0 / layer as f64;70    let step = |ground: u8, tone: u8| {71        (f64::from(ground) + (f64::from(tone) - f64::from(ground)) * t).round() as u872    };73    let (ground, dim) = (ink.ground, ink.dim);74    [75        step(ground.r, dim.r),76        step(ground.g, dim.g),77        step(ground.b, dim.b),78        255,79    ]80}8182/// Reads the window the stack lights in the dimension: the lit points, their density, the limit one over zeta, the constant the count recovers, the value it walks to and the gap between them, as JSON.83#[wasm_bindgen]84pub fn visible_read(n: usize, dimension: u32) -> Result<String, Fault> {85    let (n, dimension) = (window(n)?, depth(dimension)?);86    let lit = series::visible(n, dimension);87    let total = (n as u128).pow(dimension);88    let constant = lattice::recovered(n, dimension);89    let even = dimension.is_multiple_of(2);90    let truth = if even {91        std::f64::consts::PI92    } else {93        lattice::zeta_whole(dimension)94    };95    Ok(json!({96        "n": n,97        "dimension": dimension,98        "lit": lit.to_string(),99        "total": total.to_string(),100        "density": lit as f64 / total as f64,101        "limit": lattice::visible_density(dimension),102        "name": if even { "pi".to_string() } else { format!("zeta({dimension})") },103        "constant": constant,104        "truth": truth,105        "error": (constant - truth).abs(),106    })107    .to_string())108}109110/// Paints the corner window of the plane lattice at the pixel side asked for, the origin at the lower left: a point of coprime coordinates in blue, a hidden point in the dim of the stack layer that owns it, flat when the layers are off.111#[wasm_bindgen]112pub fn visible_pixels(n: usize, side: usize, layers: bool) -> Result<Pixels, Fault> {113    let n = window(n)?;114    if !(16..=SHEET).contains(&side) {115        return Err(Fault::new(format!(116            "the side must be between 16 and {SHEET} pixels."117        )));118    }119    let mut colors = Vec::with_capacity(side * side);120    for py in 0..side {121        let b = n - py * n / side;122        for px in 0..side {123            let a = px * n / side + 1;124            colors.push(shade(gcd(a, b), layers));125        }126    }127    Ok(Pixels::of(side, side, colors))128}129130/// Walks the window up to n at the count of stops and returns each stop as a window and the constant its count recovers, two numbers a stop, so the approach can be drawn.131#[wasm_bindgen]132pub fn visible_walk(n: usize, dimension: u32, stops: usize) -> Result<Vec<f64>, Fault> {133    let (n, dimension) = (window(n)?, depth(dimension)?);134    if !(2..=STOPS).contains(&stops) {135        return Err(Fault::new(format!(136            "the stops must be between 2 and {STOPS}."137        )));138    }139    let stops = stops.min(n);140    let mut out = Vec::with_capacity(stops * 2);141    for k in 1..=stops {142        let at = (n * k / stops).max(1);143        out.push(at as f64);144        out.push(lattice::recovered(at, dimension));145    }146    Ok(out)147}