sumset.rs

5.3 kB · rust · 141 lines

1use crate::Fault;2use mrlyrs::core::json;3use mrlyrs::num::sumset::{pairs, Sumset};4use std::cell::RefCell;5use wasm_bindgen::prelude::*;67const LOWEST: u32 = 6;8const FIRST: u32 = 6;9const HIGHEST: u32 = 16;10const CELLS: u32 = 4096;1112thread_local! {13    static HELD: RefCell<Option<Sumset>> = const { RefCell::new(None) };14}1516fn held<T>(level: u32, read: impl FnOnce(&Sumset) -> Result<T, Fault>) -> Result<T, Fault> {17    if !(LOWEST..=HIGHEST).contains(&level) {18        return Err(Fault::new(format!(19            "the height runs from 3^{LOWEST} to 3^{HIGHEST}, not 3^{level}."20        )));21    }22    HELD.with(|slot| {23        let mut slot = slot.borrow_mut();24        if slot.as_ref().map(Sumset::level) != Some(level) {25            *slot = None;26            *slot = Some(Sumset::new(level)?);27        }28        match slot.as_ref() {29            Some(sumset) => read(sumset),30            None => Err(Fault::new("the sumset was not built.")),31        }32    })33}3435/// Reads `S = A + B` at one integer as JSON: `top = 3^level`, `x`, `count = card(S meet [1, x])`, `density = D(x)` and whether `x` is a member.36///37/// `A` holds the integers whose base-3 digits are all `0` or `1`, `B` those whose base-4 digits38/// are; one bit array of `S meet [0, 3^level]` from `mrlyrs::num::sumset::Sumset` is held between39/// calls and rebuilt when the level changes. The level runs from 6 to 16, `x` from 1 to `3^level`.40#[wasm_bindgen]41pub fn sumset_read(level: u32, x: u32) -> Result<String, Fault> {42    held(level, |sumset| {43        let at = u64::from(x);44        let (Some(count), Some(density), Some(member)) =45            (sumset.count(at), sumset.density(at), sumset.contains(at))46        else {47            return Err(Fault::new(format!(48                "x runs from 1 to {}, not {x}.",49                sumset.top()50            )));51        };52        Ok(json!({53            "level": level,54            "top": sumset.top(),55            "x": x,56            "count": count,57            "density": density,58            "member": member,59        })60        .to_string())61    })62}6364/// Reads the strip of `S` over the integers `[low, high)`: the share of members in each of `cells` equal runs, one run an integer when `cells = high - low`.65#[wasm_bindgen]66pub fn sumset_strip(level: u32, low: u32, high: u32, cells: u32) -> Result<Vec<f32>, Fault> {67    held(level, |sumset| {68        Ok(sumset69            .fills(u64::from(low), u64::from(high), cells as usize)?70            .into_iter()71            .map(|v| v as f32)72            .collect())73    })74}7576/// Reads the least and the greatest `D(x)` over `cells` windows of `[1, 3^level]` spread evenly in `log x`, flat as `low, high` pairs, `NaN` for a window that holds no integer.77///78/// Window `i` runs from `round(3^(level i/cells))` to the next edge, the first from `1` and the79/// last to `3^level` itself, so every dip of `D` shows at its true depth whatever the cell width.80#[wasm_bindgen]81pub fn sumset_envelope(level: u32, cells: u32) -> Result<Vec<f32>, Fault> {82    if !(1..=CELLS).contains(&cells) {83        return Err(Fault::new(format!(84            "the envelope takes 1 to {CELLS} cells, not {cells}."85        )));86    }87    held(level, |sumset| {88        let top = sumset.top();89        let edges: Vec<u64> = (0..=cells)90            .map(|i| match i {91                0 => 1,92                i if i == cells => top + 1,93                i => ((top as f64).powf(f64::from(i) / f64::from(cells)).round() as u64)94                    .clamp(1, top),95            })96            .collect();97        Ok(sumset98            .extremes(&edges)?99            .into_iter()100            .flat_map(|w| match w {101                Some((low, high)) => [low as f32, high as f32],102                None => [f32::NAN, f32::NAN],103            })104            .collect())105    })106}107108/// Lists the census pairs `(k, m)` with `k >= 6`, `4^m` within a factor `3` of `3^k` and `d(k, m) <= 3^level`, as JSON rows by `d`.109///110/// Each row holds `three = k`, `four = m`, the scaling `scale = 4^m/3^k`, the largest element111/// `largest = d(k, m)` of `A_k + B_m`, the `gap` `[first, last]` of integers in112/// `(d, min(3^k, 4^m))` that `S` misses or `null`, whether the pair is `clean` and whether it is a113/// gap `copy`, the additive `energy` `E(k, m)` as a decimal string, the energy ratio114/// `ratio = Q(k, m)`, the Cauchy-Schwarz `bound = 1/Q` on the fill, and the `fill`115/// `card(S meet [0, d])/(d + 1)` read off the held bit array.116#[wasm_bindgen]117pub fn sumset_pairs(level: u32) -> Result<String, Fault> {118    held(level, |sumset| {119        let mut rows = Vec::new();120        for pair in pairs(level)?.into_iter().filter(|p| p.three >= FIRST) {121            let d = pair.largest();122            let energy = pair.energy();123            let ratio = pair.ratio(energy);124            let fill = sumset.count(d).map(|c| (c + 1) as f64 / (d + 1) as f64);125            rows.push(json!({126                "three": pair.three,127                "four": pair.four,128                "scale": pair.scale(),129                "largest": d,130                "gap": pair.gap().map(|(a, b)| [a, b]),131                "clean": pair.clean(),132                "copy": pair.copy(),133                "energy": energy.to_string(),134                "ratio": ratio,135                "bound": 1.0 / ratio,136                "fill": fill,137            }));138        }139        Ok(json!(rows).to_string())140    })141}