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}