carry.rs

6.4 kB · rust · 196 lines

1use crate::Fault;2use mrlycore::{json, Json};3use mrlymath::dim::carry;4use mrlynum::blend;5use wasm_bindgen::prelude::*;67const LEVELS: usize = 32;8const WIDEST: usize = 60;910// SPELLING1112fn spell(value: f64) -> String {13    if !value.is_finite() {14        return "none".to_string();15    }16    let text = format!("{value:.4}");17    let text = text.trim_end_matches('0').trim_end_matches('.');18    if text == "-0" {19        "0".to_string()20    } else {21        text.to_string()22    }23}2425fn ratios(terms: &[i128]) -> Vec<String> {26    terms27        .windows(2)28        .map(|pair| spell(pair[1] as f64 / pair[0] as f64))29        .collect()30}3132fn polynomial_text(poly: &[i128]) -> String {33    let top = poly.len().saturating_sub(1);34    let mut text = String::new();35    for (index, &weight) in poly.iter().enumerate() {36        if weight == 0 {37            continue;38        }39        let power = top - index;40        if text.is_empty() {41            if weight < 0 {42                text.push('-');43            }44        } else {45            text.push_str(if weight < 0 { " - " } else { " + " });46        }47        let shown = weight.abs() != 1 || power == 0;48        if shown {49            text.push_str(&weight.abs().to_string());50        }51        if power > 0 {52            if shown {53                text.push(' ');54            }55            text.push('x');56            if power > 1 {57                text.push_str(&format!("^{power}"));58            }59        }60    }61    if text.is_empty() {62        text.push('0');63    }64    text65}6667fn decimals(terms: &[i128]) -> Vec<String> {68    terms.iter().map(|term| term.to_string()).collect()69}7071// READINGS7273fn logarithm(value: f64, base: usize) -> f64 {74    value.ln() / (base as f64).ln()75}7677fn reading(base: usize, dimension: usize) -> Result<Json, Fault> {78    let block = carry::even_block(base, dimension)?;79    let root = carry::perron(&block)?;80    let full = carry::fill(base, dimension)? as f64;81    Ok(json!({82        "sign": carry::sign(base, dimension)?,83        "root": root,84        "gap": root - full / base as f64,85        "log_root": logarithm(root, base),86        "log_fill": logarithm(full, base) - 1.0,87    }))88}8990/// The widest dimension the exact carry arithmetic reaches at the base.91#[wasm_bindgen]92pub fn carry_cap(base: usize) -> Result<usize, Fault> {93    Ok(carry::cap(base)?)94}9596/// Reads the base-`q` slice carry automaton in dimension `D`, as JSON.97///98/// The digit polynomial, the reflection-even carry block with its characteristic polynomial, trace,99/// determinant and Perron root, the fill, the two exponents the sign law compares and the sign it100/// reads, the ladder of central diagonal counts with the ratios of its terms, and the smallest101/// linear recurrence those terms exhibit against the proved order `ceil(D/2)`.102#[wasm_bindgen]103pub fn carry_block(base: usize, dimension: usize, levels: usize) -> Result<String, Fault> {104    if !(1..=LEVELS).contains(&levels) {105        return Err(Fault::new(format!(106            "levels must be between 1 and {LEVELS}."107        )));108    }109    let top = carry::cap(base)?;110    if !(2..=top).contains(&dimension) {111        return Err(Fault::new(format!(112            "base {base} carries the dimensions 2 to {top} in exact integers."113        )));114    }115    let block = carry::even_block(base, dimension)?;116    let poly = carry::characteristic(&block)?;117    let terms = carry::ladder(base, dimension, levels)?;118    let order = dimension.div_ceil(2);119    let rule = blend::recurrence(&terms);120    let found = rule.as_ref().map(|rule| rule.len());121    Ok(json!({122        "base": base,123        "dimension": dimension,124        "cap": top,125        "order": order,126        "digits": carry::digit_polynomial(base, dimension)?,127        "block": block,128        "characteristic": decimals(&poly),129        "polynomial": polynomial_text(&poly),130        "trace": carry::trace(&block).to_string(),131        "determinant": carry::determinant(&block)?.to_string(),132        "fill": carry::fill(base, dimension)?.to_string(),133        "read": reading(base, dimension)?,134        "law": if dimension.is_multiple_of(2) { -1 } else { 1 },135        "open": dimension % 2 == 1 && dimension % 3 == 1,136        "terms": decimals(&terms),137        "ratios": ratios(&terms),138        "levels": terms.len() - 1,139        "capped": terms.len() <= levels,140        "found": json!(found),141        "fits": found == Some(order),142        "spectral": json!(carry::spectral_ratio(base, dimension)?),143    })144    .to_string())145}146147/// Walks the slice sign law over the dimensions two to the top at both bases, as JSON.148///149/// Each row carries the parity the law predicts, whether the dimension sits in the odd residue150/// class the shelf leaves open, and the sign the exact integers read at base three and base five,151/// null where the dimension passes that base's exact cap.152#[wasm_bindgen]153pub fn carry_signs(top: usize) -> Result<String, Fault> {154    if !(2..=WIDEST).contains(&top) {155        return Err(Fault::new(format!(156            "the top must be between 2 and {WIDEST}."157        )));158    }159    let rows: Vec<Json> = (2..=top)160        .map(|dimension| {161            let read = |base: usize| reading(base, dimension).unwrap_or(Json::Null);162            json!({163                "dimension": dimension,164                "order": dimension.div_ceil(2),165                "law": if dimension.is_multiple_of(2) { -1 } else { 1 },166                "open": dimension % 2 == 1 && dimension % 3 == 1,167                "three": read(3),168                "five": read(5),169            })170        })171        .collect();172    Ok(json!(rows).to_string())173}174175/// Walks the even block's spectral ratio over the dimensions four to the top, as JSON.176///177/// The ratio of the Perron root to the second eigenvalue's modulus against the free bound178/// `(D + 2)/(D - 2)` it falls to, so the vanishing spectral gap is a drawing and not a claim.179#[wasm_bindgen]180pub fn carry_ratios(base: usize, top: usize) -> Result<String, Fault> {181    if !(4..=WIDEST).contains(&top) {182        return Err(Fault::new(format!(183            "the top must be between 4 and {WIDEST}."184        )));185    }186    let rows: Vec<Json> = (4..=top)187        .map(|dimension| {188            json!({189                "dimension": dimension,190                "ratio": json!(carry::spectral_ratio(base, dimension).ok().flatten()),191                "free": (dimension as f64 + 2.0) / (dimension as f64 - 2.0),192            })193        })194        .collect();195    Ok(json!(rows).to_string())196}