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}