modes.rs

5.5 kB · rust · 179 lines

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