quasi.rs
4.4 kB · rust · 155 lines
1use mrlymath::bang::Code;2use mrlymath::formulas::fill;3use num_bigint::{BigInt, Sign};4use num_rational::BigRational;56pub type Poly = Vec<BigRational>;78const EXTRA: usize = 5;910fn rational(value: i128) -> BigRational {11 BigRational::from_integer(BigInt::from(value))12}1314fn is_zero(value: &BigRational) -> bool {15 value.numer().sign() == Sign::NoSign16}1718pub fn trim(mut poly: Poly) -> Poly {19 while poly.last().is_some_and(is_zero) {20 poly.pop();21 }22 poly23}2425fn add(a: &[BigRational], b: &[BigRational]) -> Poly {26 let zero = rational(0);27 let width = a.len().max(b.len());28 (0..width)29 .map(|i| a.get(i).unwrap_or(&zero) + b.get(i).unwrap_or(&zero))30 .collect()31}3233fn multiply(a: &[BigRational], b: &[BigRational]) -> Poly {34 let mut out = vec![rational(0); a.len() + b.len() - 1];35 for (i, x) in a.iter().enumerate() {36 for (j, y) in b.iter().enumerate() {37 out[i + j] += x * y;38 }39 }40 out41}4243pub fn evaluate(poly: &[BigRational], x: i128) -> BigRational {44 let point = rational(x);45 poly.iter()46 .rev()47 .fold(rational(0), |acc, coefficient| acc * &point + coefficient)48}4950pub fn interpolate(points: &[(i128, u128)]) -> Poly {51 let mut out = Poly::new();52 for (i, &(xi, yi)) in points.iter().enumerate() {53 let mut term = vec![rational(yi as i128)];54 for (j, &(xj, _)) in points.iter().enumerate() {55 if j != i {56 let scale = rational(xi - xj);57 term = multiply(&term, &[-rational(xj) / &scale, rational(1) / &scale]);58 }59 }60 out = add(&out, &term);61 }62 trim(out)63}6465pub fn degree(poly: &[BigRational]) -> Option<usize> {66 trim(poly.to_vec()).len().checked_sub(1)67}6869pub fn leading(poly: &[BigRational]) -> BigRational {70 trim(poly.to_vec())71 .last()72 .cloned()73 .unwrap_or_else(|| rational(0))74}7576pub fn fit(code: Code, dimension: usize, base: usize) -> (Vec<Poly>, bool) {77 let polys: Vec<Poly> = (0..base)78 .map(|class| {79 let points: Vec<(i128, u128)> = (0..)80 .map(|j| class + j * base)81 .filter(|&n| n >= 1)82 .take(dimension + 1 + EXTRA)83 .map(|n| {84 let value = fill(code, n, dimension, 1, base).expect("the fill counts");85 (n as i128, value)86 })87 .collect();88 let poly = interpolate(&points[..=dimension]);89 for &(x, y) in &points {90 assert!(91 evaluate(&poly, x) == rational(y as i128),92 "the class polynomial misses the fill"93 );94 }95 poly96 })97 .collect();98 let collapses = polys.iter().all(|poly| *poly == polys[0]);99 (polys, collapses)100}101102pub fn fraction(value: &BigRational) -> String {103 if value.denom() == &BigInt::from(1) {104 value.numer().to_string()105 } else {106 format!("{}/{}", value.numer(), value.denom())107 }108}109110fn term(magnitude: &BigRational, degree: usize) -> String {111 if degree == 0 {112 return fraction(magnitude);113 }114 let monomial = if degree == 1 {115 String::from("n")116 } else {117 format!("n**{degree}")118 };119 let one = BigInt::from(1);120 match (magnitude.numer() == &one, magnitude.denom() == &one) {121 (true, true) => monomial,122 (true, false) => format!("{monomial}/{}", magnitude.denom()),123 (false, true) => format!("{}*{monomial}", magnitude.numer()),124 (false, false) => format!("{}*{monomial}/{}", magnitude.numer(), magnitude.denom()),125 }126}127128pub fn text(poly: &[BigRational]) -> String {129 let trimmed = trim(poly.to_vec());130 if trimmed.is_empty() {131 return String::from("0");132 }133 let mut out = String::new();134 for degree in (0..trimmed.len()).rev() {135 let coefficient = &trimmed[degree];136 if is_zero(coefficient) {137 continue;138 }139 let negative = coefficient.numer().sign() == Sign::Minus;140 let magnitude = if negative {141 -coefficient.clone()142 } else {143 coefficient.clone()144 };145 if out.is_empty() {146 if negative {147 out.push('-');148 }149 } else {150 out.push_str(if negative { " - " } else { " + " });151 }152 out.push_str(&term(&magnitude, degree));153 }154 out155}