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}