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}