modes.rs
5.5 kB · rust · 178 lines
1use crate::{checked, Fault};2use mrlymath::two;3use std::f64::consts::TAU;4use wasm_bindgen::prelude::*;56const REACH: usize = 256;78// MASK910struct Mask {11 side: usize,12 level: usize,13 span: usize,14 digits: Vec<(usize, usize)>,15 wheel: Vec<(f64, f64)>,16}1718impl Mask {19 fn read(code: &str, number: usize, level: usize, base: usize) -> Result<Mask, Fault> {20 if number < 2 {21 return Err(Fault::new("the side must be at least two."));22 }23 if level < 1 {24 return Err(Fault::new("the level must be at least one."));25 }26 let span = number27 .checked_pow(level as u32)28 .filter(|&span| span <= REACH)29 .ok_or_else(|| {30 Fault::new(format!(31 "side {number} at level {level} passes the {REACH} sites a side the torus allows."32 ))33 })?;34 let tile = two::create(checked(code, 2, base)?, number, 1, 0, base)?;35 let types = tile.types().bytes().to_vec();36 let digits: Vec<(usize, usize)> = (0..number * number)37 .filter(|&at| types[at] != 0)38 .map(|at| (at / number, at % number))39 .collect();40 if digits.is_empty() {41 return Err(Fault::new("the empty design carries no modes."));42 }43 let wheel = (0..span)44 .map(|step| {45 let angle = TAU * step as f64 / span as f64;46 (angle.cos(), angle.sin())47 })48 .collect();49 Ok(Mask {50 side: number,51 level,52 span,53 digits,54 wheel,55 })56 }5758 fn mass(&self) -> f64 {59 (self.digits.len() as f64).powi(self.level as i32)60 }6162 fn value(&self, t1: usize, t2: usize) -> (f64, f64) {63 let (mut re, mut im) = (1.0, 0.0);64 let mut scale = 1usize;65 for _ in 0..self.level {66 let (mut fr, mut fi) = (0.0, 0.0);67 for &(a, b) in &self.digits {68 let dot = (a * t1 + b * t2) % self.span;69 let (c, s) = self.wheel[(dot * scale) % self.span];70 fr += c;71 fi += s;72 }73 let next = (re * fr - im * fi, re * fi + im * fr);74 re = next.0;75 im = next.1;76 scale = (scale * self.side) % self.span;77 }78 (re, im)79 }80}8182// EXPORTS8384/// Reads the eigenvalue field of a design's level-L mask: `|lambda(t)| / k^L` row-major on the `q^L` by `q^L` frequency torus, so `t = 0` reads one.85///86/// The design is the plane code at side `q` and residue base, whose level-one filled cells are87/// the digit set `F` of `k = |F|` offsets. The mask operator lays `S_L` over every site, its88/// eigenvalue at the character `e(<t, x> / q^L)` is the product of `L` rescaled copies of the89/// tile's own transform, and the side is refused past 256.90#[wasm_bindgen]91pub fn modes_field(92 code: &str,93 number: usize,94 level: usize,95 base: usize,96) -> Result<Vec<f32>, Fault> {97 let mask = Mask::read(code, number, level, base)?;98 let mass = mask.mass();99 let mut field = Vec::with_capacity(mask.span * mask.span);100 for t1 in 0..mask.span {101 for t2 in 0..mask.span {102 let (re, im) = mask.value(t1, t2);103 field.push((re.hypot(im) / mass) as f32);104 }105 }106 Ok(field)107}108109/// Reads the eigenvalue at one frequency as the three numbers `re`, `im` and `|lambda| / k^L`.110///111/// The frequency is taken on the torus, so `t1` and `t2` are read modulo `q^L`.112#[wasm_bindgen]113pub fn modes_value(114 code: &str,115 number: usize,116 level: usize,117 base: usize,118 t1: usize,119 t2: usize,120) -> Result<Vec<f64>, Fault> {121 let mask = Mask::read(code, number, level, base)?;122 let (re, im) = mask.value(t1 % mask.span, t2 % mask.span);123 Ok(vec![re, im, re.hypot(im) / mask.mass()])124}125126/// Counts the frequencies whose eigenvalue reaches the threshold, `|lambda(t)| >= threshold * k^L`, the large-values set of the mask.127///128/// A threshold of zero counts the whole torus and a threshold of one counts the frequencies the129/// mask leaves untouched, `t = 0` among them.130#[wasm_bindgen]131pub fn modes_large(132 code: &str,133 number: usize,134 level: usize,135 base: usize,136 threshold: f64,137) -> Result<usize, Fault> {138 let mask = Mask::read(code, number, level, base)?;139 let bar = threshold * mask.mass();140 let mut count = 0;141 for t1 in 0..mask.span {142 for t2 in 0..mask.span {143 let (re, im) = mask.value(t1, t2);144 if re.hypot(im) >= bar {145 count += 1;146 }147 }148 }149 Ok(count)150}151152/// Draws the real standing pattern of one frequency, `cos(2 pi <t, x> / q^L)` row-major over the torus, every value in minus one to one.153#[wasm_bindgen]154pub fn modes_pattern(155 code: &str,156 number: usize,157 level: usize,158 base: usize,159 t1: usize,160 t2: usize,161) -> Result<Vec<f32>, Fault> {162 let mask = Mask::read(code, number, level, base)?;163 let (t1, t2) = (t1 % mask.span, t2 % mask.span);164 let mut pattern = Vec::with_capacity(mask.span * mask.span);165 for x1 in 0..mask.span {166 for x2 in 0..mask.span {167 let dot = (t1 * x1 + t2 * x2) % mask.span;168 pattern.push(mask.wheel[dot].0 as f32);169 }170 }171 Ok(pattern)172}173174/// Counts the filled cells of the design's level-one tile, the digit count `k` the mass `k^L` is built from.175#[wasm_bindgen]176pub fn modes_digits(code: &str, number: usize, base: usize) -> Result<usize, Fault> {177 Ok(Mask::read(code, number, 1, base)?.digits.len())178}