gauss.rs

7.7 kB · rust · 219 lines

1use crate::spin::ramp_of;2use crate::{rgba, theme, Fault, Pixels};3use mrlycore::json;4use mrlynum::factor::factorize_wide;5use mrlynum::gauss::{peak, shells, Class, Ring, Window};6use wasm_bindgen::prelude::*;78const RADIUS: u32 = 200;9const SIZE: usize = 768;10const LIMIT: usize = 10_000;11const ROOT3: f64 = 1.732_050_807_568_877_2;1213struct Sheet {14    window: Window,15    half: f64,16    scale: f64,17}1819impl Sheet {20    fn new(ring: &str, radius: u32, size: usize) -> Result<Sheet, Fault> {21        let ring =22            Ring::named(ring).ok_or_else(|| Fault::new("the ring is gaussian or eisenstein."))?;23        if radius == 0 || radius > RADIUS {24            return Err(Fault::new(format!("the radius is 1 to {RADIUS}.")));25        }26        if size == 0 || size > SIZE {27            return Err(Fault::new(format!("the sheet is 1 to {SIZE} pixels wide.")));28        }29        Ok(Sheet {30            window: Window::new(ring, u64::from(radius)),31            half: size as f64 / 2.0,32            scale: size as f64 / (2 * radius + 1) as f64,33        })34    }35    fn ring(&self) -> Ring {36        self.window.ring()37    }38    fn center(&self, a: i64, b: i64) -> (f64, f64) {39        let (x, y) = self.ring().place(a, b);40        (self.half + x * self.scale, self.half - y * self.scale)41    }42    fn cell(&self, px: f64, py: f64) -> Option<(i64, i64)> {43        let (a, b) = self44            .ring()45            .nearest((px - self.half) / self.scale, (self.half - py) / self.scale);46        self.window.holds(a, b).then_some((a, b))47    }48    fn gap(&self, px: f64, py: f64, cell: (i64, i64)) -> bool {49        if self.scale < 6.0 {50            return false;51        }52        let (cx, cy) = self.center(cell.0, cell.1);53        let (x, y) = (px - cx, py - cy);54        match self.ring() {55            Ring::Gaussian => x + self.scale / 2.0 < 1.0 || y + self.scale / 2.0 < 1.0,56            Ring::Eisenstein => {57                let reach = x58                    .abs()59                    .max((x / 2.0 + y * ROOT3 / 2.0).abs())60                    .max((y * ROOT3 / 2.0 - x / 2.0).abs());61                reach > self.scale / 2.0 - 1.062            }63        }64    }65    fn spot(&self, (a, b): (i64, i64)) -> [f64; 4] {66        let (px, py) = self.center(a, b);67        [a as f64, b as f64, px, py]68    }69}7071/// Paints the window of a ring over a square sheet: primes by class, split blue, inert orange, ramified pink and the units green, or by norm through the fire ramp, or plain gold; the composites faint or dark.72#[wasm_bindgen]73pub fn ring_pixels(74    ring: &str,75    radius: u32,76    colour: &str,77    faint: bool,78    size: usize,79) -> Result<Pixels, Fault> {80    let sheet = Sheet::new(ring, radius, size)?;81    if !["class", "norm", "plain"].contains(&colour) {82        return Err(Fault::new("the colour is class, norm or plain."));83    }84    let fire = ramp_of("fire");85    let top = sheet.ring().top(u64::from(radius)) as usize;86    let side = (2 * radius + 1) as usize;87    let r = radius as i64;88    let ink = theme();89    let ground = rgba(ink.ground);90    let mut look = vec![ground; side * side];91    for b in -r..=r {92        for a in -r..=r {93            if !sheet.window.holds(a, b) {94                continue;95            }96            let class = sheet.window.class(a, b);97            look[(b + r) as usize * side + (a + r) as usize] = match (colour, class) {98                (_, Class::Zero) => ground,99                (_, Class::Unit) => rgba(ink.green),100                (_, Class::Composite) if faint => rgba(ink.line),101                (_, Class::Composite) => ground,102                ("class", Class::Split) => rgba(ink.blue),103                ("class", Class::Inert) => rgba(ink.orange),104                ("class", Class::Ramified) => rgba(ink.pink),105                ("norm", _) => {106                    let c = fire.color(sheet.ring().norm(a, b) as usize, top);107                    [c.r, c.g, c.b, 255]108                }109                _ => rgba(ink.yellow),110            };111        }112    }113    let mut colors = Vec::with_capacity(size * size);114    for py in 0..size {115        for px in 0..size {116            let (fx, fy) = (px as f64 + 0.5, py as f64 + 0.5);117            colors.push(match sheet.cell(fx, fy) {118                Some(cell) if !sheet.gap(fx, fy, cell) => {119                    look[(cell.1 + r) as usize * side + (cell.0 + r) as usize]120                }121                _ => ground,122            });123        }124    }125    Ok(Pixels::of(size, size, colors))126}127128/// Counts the window of a ring: its points, primes, split, inert, ramified, units and composites, the prime density, the largest norm and the symmetry order, as JSON.129#[wasm_bindgen]130pub fn ring_census(ring: &str, radius: u32) -> Result<String, Fault> {131    let sheet = Sheet::new(ring, radius, 1)?;132    let census = sheet.window.census();133    Ok(json!({134        "points": census.points,135        "primes": census.primes,136        "split": census.split,137        "inert": census.inert,138        "ramified": census.ramified,139        "units": census.units,140        "composites": census.composites,141        "density": census.density,142        "top": sheet.ring().top(u64::from(radius)),143        "symmetry": sheet.ring().symmetry(),144    })145    .to_string())146}147148/// Reads the point under a pixel of the sheet: its coordinates, norm and its factors, class and primality, its pixel centre and width, and the places of its unit multiples and its conjugate, as JSON.149#[wasm_bindgen]150pub fn ring_at(ring: &str, radius: u32, x: f64, y: f64, size: usize) -> Result<String, Fault> {151    let sheet = Sheet::new(ring, radius, size)?;152    let (a, b) = sheet153        .cell(x, y)154        .ok_or_else(|| Fault::new("the click missed the window."))?;155    let class = sheet.window.class(a, b);156    let norm = sheet.ring().norm(a, b);157    let (px, py) = sheet.center(a, b);158    let associates: Vec<[f64; 4]> = sheet159        .ring()160        .associates(a, b)161        .into_iter()162        .map(|point| sheet.spot(point))163        .collect();164    Ok(json!({165        "a": a,166        "b": b,167        "norm": norm,168        "factors": factorize_wide(norm),169        "class": class.word(),170        "prime": class.prime(),171        "px": px,172        "py": py,173        "span": sheet.scale,174        "associates": associates,175        "conjugate": sheet.spot(sheet.ring().conjugate(a, b)),176    })177    .to_string())178}179180/// Counts the points of every norm from zero through the limit: the ring weights of the lattice.181#[wasm_bindgen]182pub fn ring_weights(ring: &str, limit: usize) -> Result<Vec<u32>, Fault> {183    let ring =184        Ring::named(ring).ok_or_else(|| Fault::new("the ring is gaussian or eisenstein."))?;185    if limit > LIMIT {186        return Err(Fault::new(format!("the weights stop at norm {LIMIT}.")));187    }188    Ok(shells(ring, limit))189}190191/// Returns the norm from one through the limit with the most points and that count.192#[wasm_bindgen]193pub fn ring_peak(ring: &str, limit: usize) -> Result<Vec<u32>, Fault> {194    let ring =195        Ring::named(ring).ok_or_else(|| Fault::new("the ring is gaussian or eisenstein."))?;196    if limit > LIMIT {197        return Err(Fault::new(format!("the weights stop at norm {LIMIT}.")));198    }199    let (norm, count) = peak(ring, limit);200    Ok(vec![norm as u32, count])201}202203/// Reads the fate of every whole number from zero through the limit as a prime of the ring: 0 when not prime, 1 split, 2 inert, 3 ramified.204#[wasm_bindgen]205pub fn ring_fates(ring: &str, limit: usize) -> Result<Vec<u8>, Fault> {206    let ring =207        Ring::named(ring).ok_or_else(|| Fault::new("the ring is gaussian or eisenstein."))?;208    if limit > LIMIT {209        return Err(Fault::new(format!("the fates stop at {LIMIT}.")));210    }211    Ok((0..=limit as u64)212        .map(|n| match ring.fate(n) {213            Class::Split => 1,214            Class::Inert => 2,215            Class::Ramified => 3,216            _ => 0,217        })218        .collect())219}