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}