novelty.rs
4.8 kB · rust · 139 lines
1use crate::Fault;2use mrlynum::lattice::totients;3use mrlynum::zeta::{novelty_main, novelty_wave, sharp_novelty, smoothed_novelty, Complex, Line};4use wasm_bindgen::prelude::*;56const LOW: f64 = 8.0;7const HIGHEST: f64 = 21.0;8const PER_OCTAVE: usize = 32;9const ZEROS: usize = 138;1011/// The novelty meter: the totients sieved once, the smoothed and the sharp novelty error read on a log grid of y, and the first zeros with the coefficients of their waves.12#[wasm_bindgen]13pub struct Novelty {14 heights: Vec<f64>,15 smooth: Vec<f64>,16 rough: Vec<f64>,17 gammas: Vec<f64>,18 coef: Vec<Complex>,19 sieve: usize,20}2122#[wasm_bindgen]23impl Novelty {24 /// Builds the meter on y = 2^-j for j from eight to the height at the given samples per octave, the totients sieved to two to the height plus one, with the given count of zeros, at most 138.25 #[wasm_bindgen(constructor)]26 pub fn new(high: f64, per_octave: usize, zeros: usize) -> Result<Novelty, Fault> {27 if !(LOW + 1.0..=HIGHEST).contains(&high) {28 return Err(Fault::new(format!(29 "the height runs from {} to {HIGHEST}.",30 LOW + 1.031 )));32 }33 if !(1..=PER_OCTAVE).contains(&per_octave) {34 return Err(Fault::new(format!(35 "an octave takes 1 to {PER_OCTAVE} samples."36 )));37 }38 if zeros > ZEROS {39 return Err(Fault::new(format!("the meter holds {ZEROS} zeros.")));40 }41 let sieve = 2.0f64.powf(high + 1.0).ceil() as usize;42 let phi = totients(sieve);43 let mut prefix = vec![0u64; sieve + 1];44 for n in 1..=sieve {45 prefix[n] = prefix[n - 1] + phi[n];46 }47 let main = novelty_main();48 let samples = ((high - LOW) * per_octave as f64).round() as usize + 1;49 let heights: Vec<f64> = (0..samples)50 .map(|k| LOW + k as f64 / per_octave as f64)51 .collect();52 let smooth = heights53 .iter()54 .map(|&j| {55 let y = 2.0f64.powf(-j);56 smoothed_novelty(&phi, y, main) / y.powf(1.5)57 })58 .collect();59 let rough = heights60 .iter()61 .map(|&j| {62 let y = 2.0f64.powf(-j);63 sharp_novelty(&prefix, y) / y64 })65 .collect();66 let line = Line::new();67 let gammas = line.zeros(zeros);68 let coef = line.novelty_coefficients(&gammas);69 Ok(Novelty {70 heights,71 smooth,72 rough,73 gammas,74 coef,75 sieve,76 })77 }78 fn first(&self, count: usize) -> Result<usize, Fault> {79 if count > self.gammas.len() {80 return Err(Fault::new(format!(81 "the meter holds {} zeros.",82 self.gammas.len()83 )));84 }85 Ok(count)86 }87 /// Returns j at every sample, the log base two of one over y.88 pub fn heights(&self) -> Vec<f64> {89 self.heights.clone()90 }91 /// Returns the error at every sample: the smoothed error over y to the three halves, or the sharp window's error over y.92 pub fn dots(&self, sharp: bool) -> Vec<f64> {93 if sharp {94 self.rough.clone()95 } else {96 self.smooth.clone()97 }98 }99 /// Returns the ordinate of every zero the meter holds.100 pub fn gammas(&self) -> Vec<f64> {101 self.gammas.clone()102 }103 /// Returns the modulus of every zero's wave coefficient.104 pub fn amplitudes(&self) -> Vec<f64> {105 self.coef.iter().map(|c| c.abs()).collect()106 }107 /// Sums the waves of the first zeros at the given heights j, on the scale of the dots: over y to the three halves, or over y when sharp, where the waves shrink by the root of y.108 pub fn wave(&self, count: usize, sharp: bool, heights: &[f64]) -> Result<Vec<f64>, Fault> {109 let count = self.first(count)?;110 let log2 = 2.0f64.ln();111 Ok(heights112 .iter()113 .map(|&j| {114 let sum = novelty_wave(&self.gammas[..count], &self.coef[..count], -j * log2);115 if sharp {116 sum * 2.0f64.powf(-0.5 * j)117 } else {118 sum119 }120 })121 .collect())122 }123 /// Returns the largest gap between the smoothed dots and the wave of the first zeros, over the largest dot.124 pub fn miss(&self, count: usize) -> Result<f64, Fault> {125 let wave = self.wave(count, false, &self.heights)?;126 let peak = self.smooth.iter().fold(0.0f64, |a, v| a.max(v.abs()));127 let gap = self128 .smooth129 .iter()130 .zip(&wave)131 .map(|(d, w)| (d - w).abs())132 .fold(0.0f64, f64::max);133 Ok(gap / peak)134 }135 /// Returns the reach of the totient sieve.136 pub fn sieve(&self) -> usize {137 self.sieve138 }139}