use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::float::{Float, RoundingMode};
use crate::int::Int;
use crate::lattice::pslq;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Identification {
x_coeff: Int,
terms: Vec<(Int, String)>,
}
impl Identification {
pub fn x_coeff(&self) -> &Int {
&self.x_coeff
}
pub fn terms(&self) -> &[(Int, String)] {
&self.terms
}
}
fn write_term(out: &mut String, coeff: &Int, name: &str, leading: bool) {
use core::fmt::Write as _;
let neg = coeff.is_negative();
if leading {
if neg {
out.push('\u{2212}'); }
} else if neg {
out.push_str(" \u{2212} ");
} else {
out.push_str(" + ");
}
let mag = coeff.abs();
if name == "1" {
let _ = write!(out, "{mag}");
} else if mag.is_one() {
out.push_str(name);
} else {
let _ = write!(out, "{mag}\u{b7}{name}"); }
}
impl fmt::Display for Identification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.terms.is_empty() {
return f.write_str("0");
}
let mut num = String::new();
for (i, (coeff, name)) in self.terms.iter().enumerate() {
write_term(&mut num, coeff, name, i == 0);
}
if self.x_coeff.is_one() {
f.write_str(&num)
} else if self.terms.len() > 1 {
write!(f, "({num})/{}", self.x_coeff)
} else {
write!(f, "{num}/{}", self.x_coeff)
}
}
}
fn default_basis(precision: u64) -> Vec<(&'static str, Float)> {
let m = RoundingMode::Nearest;
let one = Float::from_int(&Int::ONE, precision, m);
let pi = Float::pi(precision, m);
let pi2 = pi.mul(&pi, precision, m);
let e = Float::e(precision, m);
let ln2 = Float::ln2(precision, m);
let gamma = Float::euler_gamma(precision, m);
let catalan = Float::catalan(precision, m);
let zeta3 = Float::from_int(&Int::from_i64(3), precision, m).zeta(precision, m);
let sqrt = |k: i64| Float::from_int(&Int::from_i64(k), precision, m).sqrt(precision, m);
alloc::vec![
("1", one),
("π", pi),
("π²", pi2),
("e", e),
("ln2", ln2),
("γ", gamma),
("G", catalan),
("ζ(3)", zeta3),
("√2", sqrt(2)),
("√3", sqrt(3)),
("√5", sqrt(5)),
]
}
pub fn identify(x: &Float, precision: u64) -> Option<Identification> {
identify_with(x, precision, &default_basis(precision))
}
pub fn identify_with(x: &Float, precision: u64, basis: &[(&str, Float)]) -> Option<Identification> {
let mut xs = Vec::with_capacity(basis.len() + 1);
xs.push(x.clone());
for (_, c) in basis {
xs.push(c.clone());
}
let rel = pslq(&xs, precision)?;
let x_coeff = rel[0].clone();
if x_coeff.is_zero() {
return None;
}
let mut named = Vec::new();
let mut ones = Vec::new();
for (i, (name, _)) in basis.iter().enumerate() {
let ai = &rel[i + 1];
if ai.is_zero() {
continue;
}
let entry = (ai.neg(), String::from(*name));
if *name == "1" {
ones.push(entry);
} else {
named.push(entry);
}
}
named.extend(ones);
Some(Identification {
x_coeff,
terms: named,
})
}
pub fn machin_like(denominators: &[i64], precision: u64) -> Option<Vec<Int>> {
let m = RoundingMode::Nearest;
let one = Float::from_int(&Int::ONE, precision, m);
let mut xs = Vec::with_capacity(denominators.len() + 1);
let four = Float::from_int(&Int::from_i64(4), precision, m);
xs.push(Float::pi(precision, m).div(&four, precision, m));
for &n in denominators {
if n < 2 {
return None;
}
let nf = Float::from_int(&Int::from_i64(n), precision, m);
let recip = one.div(&nf, precision, m);
xs.push(recip.atan(precision, m));
}
pslq(&xs, precision)
}