dissection.rs
7.6 kB · rust · 217 lines
1use crate::Fault;2use mrlyrs::core::json;3use mrlyrs::num::dissection::{self as cut, Region};4use wasm_bindgen::prelude::*;56const LEAST: u32 = 3;7const MOST: u32 = 128;8const SPAN: u64 = 1 << 24;9const MASS: u64 = 1 << 21;10const GRID: u64 = 1 << 16;11const SAMPLES: usize = 720;12const CHAIN: u64 = 4096;1314fn set(base: u32, digit: u32) -> Result<Vec<u64>, Fault> {15 if !(LEAST..=MOST).contains(&base) {16 return Err(Fault::new(format!(17 "the base runs from {LEAST} to {MOST}, not {base}."18 )));19 }20 if digit >= base {21 return Err(Fault::new(format!(22 "the missing digit sits below the base {base}, not at {digit}."23 )));24 }25 Ok((0..u64::from(base))26 .filter(|&d| d != u64::from(digit))27 .collect())28}2930fn deepest(base: u32, span: u64) -> u32 {31 let mut level = 0;32 while u64::from(base).pow(level + 1) <= span {33 level += 1;34 }35 level36}3738fn index(region: Region) -> usize {39 match region {40 Region::A => 0,41 Region::B => 1,42 Region::C1 => 2,43 Region::C2 => 3,44 }45}4647/// Reads the set missing one digit as JSON: its fill, dimension, `kappa_F`, the consecutive pair, the unshifted masses to the deepest level inside `2^21` frequencies and the exponent they read, the chain certificate at the base, the bars `1/5` and `1/4`, the walls and how the theorem reaches the set, and the depths the other reads allow.48///49/// The base runs from 3 to 128; the masses come from `mrlyrs::num::dissection::masses`, the chain50/// from `chain_exponent` and `chain_margin`, and `reach` from `mrlyrs::num::dissection::reach`:51/// `proof` from the chain's wall 584, `certificate` from base 115 and at the certified sets below52/// it, `none` elsewhere.53#[wasm_bindgen]54pub fn dissection_read(base: u32, digit: u32) -> Result<String, Fault> {55 let digits = set(base, digit)?;56 let q = u64::from(base);57 let fill = digits.len();58 let level = deepest(base, MASS);59 let masses = cut::masses(q, &digits, level);60 let (top, bottom) = cut::kappa(q, &digits);61 let wall = cut::chain_wall();62 let reach = cut::reach(q, u64::from(digit));63 Ok(json!({64 "base": base,65 "digit": digit,66 "digits": digits,67 "fill": fill,68 "alpha": (fill as f64).ln() / (q as f64).ln(),69 "consecutive": cut::consecutive(&digits),70 "kappa": [top, bottom],71 "kappaValue": top as f64 / bottom as f64,72 "level": level,73 "masses": masses,74 "reading": cut::reading(q, fill, &masses),75 "chain": cut::chain_exponent(q),76 "margin": cut::chain_margin(q),77 "bars": [cut::BAR_A, cut::BAR_B],78 "walls": { "chain": wall, "window": cut::WINDOW_WALL, "digit": cut::DIGIT_WALL, "first": cut::FIRST_BELOW },79 "reach": reach,80 "depths": [LEAST, deepest(base, SPAN)],81 "grids": [1, deepest(base, GRID)],82 })83 .to_string())84}8586/// The set's own sums on a log grid of `x`: the meter against its two yardsticks and the prime count against its main term.87#[wasm_bindgen(getter_with_clone)]88pub struct Mertens {89 /// The log of `x` at every sample.90 pub logx: Vec<f32>,91 /// `M_F(x)/A_F(x)` at every sample.92 pub mass: Vec<f32>,93 /// `M_F(x)/A_F(x)^(1/2)` at every sample.94 pub root: Vec<f32>,95 /// `psi_F(x)/(kappa_F A_F(x))` at every sample.96 pub primes: Vec<f32>,97 /// The reading at the last sample, as JSON.98 pub read: String,99}100101/// Tallies the set below `base^level` on 720 points uniform in `log x`: the meter `M_F(x)` over the mass `A_F(x)` and over its square root, and `psi_F(x) = sum Lambda(n)` over `kappa_F A_F(x)`.102///103/// The Mobius values and the primes are sieved to the span, which stays inside `2^24`.104#[wasm_bindgen]105pub fn dissection_tally(base: u32, digit: u32, level: u32) -> Result<Mertens, Fault> {106 let digits = set(base, digit)?;107 let most = deepest(base, SPAN);108 if !(LEAST..=most).contains(&level) {109 return Err(Fault::new(format!(110 "the digits of x run from {LEAST} to {most} at base {base}."111 )));112 }113 let q = u64::from(base);114 let (top, bottom) = cut::kappa(q, &digits);115 let kappa = top as f64 / bottom as f64;116 let t = cut::tally(q, &digits, level as usize, SAMPLES);117 let mass: Vec<f64> = t118 .meter119 .iter()120 .zip(&t.count)121 .map(|(&m, &a)| m as f64 / a as f64)122 .collect();123 let root: Vec<f64> = t124 .meter125 .iter()126 .zip(&t.count)127 .map(|(&m, &a)| m as f64 / (a as f64).sqrt())128 .collect();129 let primes: Vec<f64> = t130 .primes131 .iter()132 .zip(&t.count)133 .map(|(&p, &a)| p / (kappa * a as f64))134 .collect();135 let last = t.count.len() - 1;136 let peak = root.iter().fold(0.0f64, |high, v| high.max(v.abs()));137 let read = json!({138 "top": q.pow(level),139 "count": t.count[last],140 "meter": t.meter[last],141 "psi": t.primes[last],142 "mass": mass[last],143 "root": root[last],144 "primes": primes[last],145 "peak": peak,146 "samples": SAMPLES,147 })148 .to_string();149 Ok(Mertens {150 logx: t.log_x.iter().map(|&v| v as f32).collect(),151 mass: mass.iter().map(|&v| v as f32).collect(),152 root: root.iter().map(|&v| v as f32).collect(),153 primes: primes.iter().map(|&v| v as f32).collect(),154 read,155 })156}157158/// The frequency grid `a/base^level` cut into the four regions, each frequency with its weight.159#[wasm_bindgen(getter_with_clone)]160pub struct Regions {161 /// The region of every frequency: 0 for A, 1 for B, 2 for C1, 3 for C2.162 pub region: Vec<u8>,163 /// `|hat F_level(a/y)|/fill^level` at every frequency.164 pub weight: Vec<f32>,165 /// The counts, the `l^1` shares and the cut, as JSON.166 pub read: String,167}168169/// Cuts the grid of `y = base^level` frequencies at `Q = y^(3/5)` and the given `Z` into the regions A, B, C1 and C2, weighs each by the digit transform, and reads the count and the share of the `l^1` mass in each region.170#[wasm_bindgen]171pub fn dissection_grid(base: u32, digit: u32, level: u32, z: u32) -> Result<Regions, Fault> {172 let digits = set(base, digit)?;173 let most = deepest(base, GRID);174 if !(1..=most).contains(&level) {175 return Err(Fault::new(format!(176 "the grid level runs from 1 to {most} at base {base}."177 )));178 }179 if z < 2 {180 return Err(Fault::new("the cut Z is at least 2."));181 }182 let q = u64::from(base);183 let y = q.pow(level);184 let regions = cut::regions(q, level, u64::from(z));185 let weights = cut::weights(q, &digits, level);186 let scale = (digits.len() as f64).powi(level as i32);187 let mut counts = [0u64; 4];188 let mut mass = [0.0f64; 4];189 for (region, weight) in regions.iter().zip(&weights) {190 counts[index(*region)] += 1;191 mass[index(*region)] += weight;192 }193 let total: f64 = mass.iter().sum();194 let shares: Vec<f64> = mass.iter().map(|m| m / total).collect();195 let read = json!({196 "y": y,197 "cap": cut::cap(y),198 "low": (y as f64).powf(0.4),199 "z": z,200 "counts": counts,201 "shares": shares,202 "mass": total,203 "reading": (total / scale).ln() / (y as f64).ln(),204 })205 .to_string();206 Ok(Regions {207 region: regions.iter().map(|&r| index(r) as u8).collect(),208 weight: weights.iter().map(|&w| (w / scale) as f32).collect(),209 read,210 })211}212213/// Returns the chain's certificate exponent `alpha_1` at one missing digit for every base from 3 to 4096, in order.214#[wasm_bindgen]215pub fn dissection_chain() -> Vec<f32> {216 (3..=CHAIN).map(|q| cut::chain_exponent(q) as f32).collect()217}