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}