#![allow(dead_code)]
use crate::kernel::{ExprId, ExprPool};
use crate::simplify::simplify;
use rug::{Integer, Rational};
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Rigor {
ProvedUnderHypotheses,
NumericallyConsistent,
}
impl Rigor {
pub fn tag(self) -> &'static str {
match self {
Rigor::ProvedUnderHypotheses => "proved_under_hypotheses",
Rigor::NumericallyConsistent => "numerically_consistent",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum HypothesisStatus {
Checked,
Assumed,
}
impl HypothesisStatus {
pub fn tag(self) -> &'static str {
match self {
HypothesisStatus::Checked => "checked",
HypothesisStatus::Assumed => "assumed",
}
}
}
#[derive(Clone, Debug)]
pub struct Hypothesis {
pub statement: String,
pub status: HypothesisStatus,
}
impl Hypothesis {
pub fn checked(statement: impl Into<String>) -> Self {
Hypothesis {
statement: statement.into(),
status: HypothesisStatus::Checked,
}
}
pub fn assumed(statement: impl Into<String>) -> Self {
Hypothesis {
statement: statement.into(),
status: HypothesisStatus::Assumed,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VerificationPoint {
pub at: f64,
pub reference: f64,
pub approximation: f64,
pub relative_error: f64,
}
#[derive(Clone, Debug)]
pub struct AsymptoticReport {
pub method: &'static str,
pub var: ExprId,
pub terms: Vec<ExprId>,
pub rigor: Rigor,
pub hypotheses: Vec<Hypothesis>,
pub verification: Vec<VerificationPoint>,
pub derivation: Vec<String>,
}
impl AsymptoticReport {
pub fn leading(&self) -> Option<ExprId> {
self.terms.first().copied()
}
pub fn partial_sum(&self, pool: &ExprPool) -> ExprId {
if self.terms.is_empty() {
return pool.integer(0_i32);
}
simplify(pool.add(self.terms.clone()), pool).value
}
pub fn max_relative_error(&self) -> Option<f64> {
self.verification
.iter()
.map(|v| v.relative_error)
.fold(None, |acc, e| Some(acc.map_or(e, |a: f64| a.max(e))))
}
pub fn all_hypotheses_checked(&self) -> bool {
self.hypotheses
.iter()
.all(|h| h.status == HypothesisStatus::Checked)
}
}
pub(crate) const DEFAULT_SLACK: f64 = 4.0;
pub(crate) fn gate_accept(oracle: &[f64], term_vals: &[Vec<f64>], slack: f64) -> usize {
if oracle.is_empty() || term_vals.is_empty() {
return 0;
}
if oracle.iter().any(|v| !v.is_finite()) {
return 0;
}
let npts = oracle.len();
if term_vals.iter().any(|row| row.len() != npts) {
return 0;
}
let mut accepted = 0usize;
let mut partial = vec![0.0f64; npts];
for (k, row) in term_vals.iter().enumerate() {
if row.iter().any(|v| !v.is_finite()) {
break;
}
if k > 0 {
let prev = &term_vals[k - 1];
let mut ok = true;
let mut last_ratio = f64::INFINITY;
for j in 0..npts {
let denom = prev[j].abs();
if denom == 0.0 {
ok = false;
break;
}
let ratio = row[j].abs() / denom;
if ratio > 1.0 {
ok = false;
break;
}
if j > 0 && ratio > last_ratio * (1.0 + 1e-9) {
ok = false;
break;
}
last_ratio = ratio;
}
if !ok {
break;
}
}
let mut next_partial = partial.clone();
for j in 0..npts {
next_partial[j] += row[j];
}
let mut residual_ok = true;
let mut rels = Vec::with_capacity(npts);
for j in 0..npts {
let residual = (oracle[j] - next_partial[j]).abs();
let scale = row[j].abs();
if residual > scale * slack + 1e-12 {
residual_ok = false;
break;
}
rels.push(if scale > 0.0 {
residual / scale
} else {
residual
});
}
if residual_ok && npts >= 2 {
let first = rels[0];
let last = rels[npts - 1];
let effectively_exact = rels.iter().all(|&r| r <= 1e-12);
if !effectively_exact && last > first * 0.99 {
residual_ok = false;
}
}
if !residual_ok {
break;
}
partial = next_partial;
accepted = k + 1;
}
accepted
}
pub(crate) fn verification_points(
points: &[f64],
oracle: &[f64],
term_vals: &[Vec<f64>],
accepted: usize,
) -> Vec<VerificationPoint> {
let mut out = Vec::with_capacity(points.len());
for (j, &at) in points.iter().enumerate() {
let mut approx = 0.0;
for row in term_vals.iter().take(accepted) {
approx += row[j];
}
let reference = oracle[j];
let denom = reference.abs().max(1e-300);
out.push(VerificationPoint {
at,
reference,
approximation: approx,
relative_error: (reference - approx).abs() / denom,
});
}
out
}
pub(crate) fn eval_over(
expr: ExprId,
var: ExprId,
points: &[f64],
pool: &ExprPool,
) -> Option<Vec<f64>> {
let mut out = Vec::with_capacity(points.len());
for &p in points {
let mut env = HashMap::new();
env.insert(var, p);
let v = crate::jit::eval_interp(expr, &env, pool)?;
if !v.is_finite() {
return None;
}
out.push(v);
}
Some(out)
}
pub(crate) fn bernoulli_numbers(m: usize) -> Vec<Rational> {
let mut b: Vec<Rational> = Vec::with_capacity(m + 1);
b.push(Rational::from(1));
for k in 1..=m {
let mut acc = Rational::from(0);
for (j, bj) in b.iter().enumerate().take(k) {
let c = binomial_int(k as u32 + 1, j as u32);
acc += Rational::from(c) * bj;
}
acc /= Rational::from(-(k as i64 + 1));
b.push(acc);
}
b
}
pub(crate) fn binomial_int(n: u32, k: u32) -> Integer {
if k > n {
return Integer::from(0);
}
let mut r = Integer::from(1);
for i in 0..k {
r *= Integer::from(n - i);
r /= Integer::from(i + 1);
}
r
}
pub(crate) fn bernoulli_poly_at(n: usize, x: &Rational, bern: &[Rational]) -> Rational {
let mut acc = Rational::from(0);
let mut xp = Rational::from(1);
for k in 0..=n {
let c = binomial_int(n as u32, k as u32);
acc += Rational::from(c) * &bern[n - k] * &xp;
xp *= x;
}
acc
}
pub(crate) type QPoly = Vec<Rational>;
pub(crate) fn qp_trim(mut p: QPoly) -> QPoly {
while p.len() > 1 && p.last().is_some_and(|c| *c == 0) {
p.pop();
}
if p.is_empty() {
p.push(Rational::from(0));
}
p
}
pub(crate) fn qp_is_zero(p: &QPoly) -> bool {
p.iter().all(|c| *c == 0)
}
pub(crate) fn qp_add(a: &QPoly, b: &QPoly) -> QPoly {
let n = a.len().max(b.len());
let mut out = vec![Rational::from(0); n];
for (i, c) in a.iter().enumerate() {
out[i] += c;
}
for (i, c) in b.iter().enumerate() {
out[i] += c;
}
qp_trim(out)
}
pub(crate) fn qp_neg(a: &QPoly) -> QPoly {
a.iter().map(|c| -c.clone()).collect()
}
pub(crate) fn qp_mul(a: &QPoly, b: &QPoly) -> QPoly {
if qp_is_zero(a) || qp_is_zero(b) {
return vec![Rational::from(0)];
}
let mut out = vec![Rational::from(0); a.len() + b.len() - 1];
for (i, x) in a.iter().enumerate() {
if *x == 0 {
continue;
}
for (j, y) in b.iter().enumerate() {
if *y == 0 {
continue;
}
let prod = Rational::from(x * y);
out[i + j] += prod;
}
}
qp_trim(out)
}
pub(crate) fn qp_pow(a: &QPoly, e: u32) -> QPoly {
let mut acc: QPoly = vec![Rational::from(1)];
for _ in 0..e {
acc = qp_mul(&acc, a);
}
acc
}
pub(crate) fn qp_eval(p: &QPoly, x: &Rational) -> Rational {
let mut acc = Rational::from(0);
for c in p.iter().rev() {
acc *= x;
acc += c;
}
acc
}
pub(crate) fn qp_degree(p: &QPoly) -> usize {
let t = qp_trim(p.clone());
if qp_is_zero(&t) {
0
} else {
t.len() - 1
}
}
pub(crate) fn qp_to_expr(p: &QPoly, var: ExprId, pool: &ExprPool) -> ExprId {
let mut terms = Vec::new();
for (i, c) in p.iter().enumerate() {
if *c == 0 {
continue;
}
let coeff = rational_to_expr(c, pool);
if i == 0 {
terms.push(coeff);
} else {
let vp = pool.pow(var, pool.integer(i as i32));
terms.push(pool.mul(vec![coeff, vp]));
}
}
if terms.is_empty() {
pool.integer(0_i32)
} else {
simplify(pool.add(terms), pool).value
}
}
pub(crate) fn rational_to_expr(q: &Rational, pool: &ExprPool) -> ExprId {
if *q.denom() == 1 {
pool.integer(q.numer().clone())
} else {
pool.rational(q.numer().clone(), q.denom().clone())
}
}
#[derive(Clone, Debug)]
pub(crate) struct QRatFn {
pub num: QPoly,
pub den: QPoly,
}
impl QRatFn {
pub fn constant(c: Rational) -> Self {
QRatFn {
num: vec![c],
den: vec![Rational::from(1)],
}
}
pub fn mul(&self, other: &QRatFn) -> QRatFn {
QRatFn {
num: qp_mul(&self.num, &other.num),
den: qp_mul(&self.den, &other.den),
}
}
pub fn recip(&self) -> Option<QRatFn> {
if qp_is_zero(&self.num) {
return None;
}
Some(QRatFn {
num: self.den.clone(),
den: self.num.clone(),
})
}
pub fn pow_int(&self, e: i32) -> Option<QRatFn> {
if e >= 0 {
Some(QRatFn {
num: qp_pow(&self.num, e as u32),
den: qp_pow(&self.den, e as u32),
})
} else {
let r = self.recip()?;
Some(QRatFn {
num: qp_pow(&r.num, (-e) as u32),
den: qp_pow(&r.den, (-e) as u32),
})
}
}
pub fn add(&self, other: &QRatFn) -> QRatFn {
QRatFn {
num: qp_add(
&qp_mul(&self.num, &other.den),
&qp_mul(&other.num, &self.den),
),
den: qp_mul(&self.den, &other.den),
}
}
}
pub(crate) fn as_rational_function(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<QRatFn> {
use crate::kernel::ExprData;
if expr == var {
return Some(QRatFn {
num: vec![Rational::from(0), Rational::from(1)],
den: vec![Rational::from(1)],
});
}
match pool.get(expr) {
ExprData::Integer(i) => Some(QRatFn::constant(Rational::from(i.0.clone()))),
ExprData::Rational(r) => Some(QRatFn::constant(r.0.clone())),
ExprData::Add(args) => {
let mut acc = QRatFn::constant(Rational::from(0));
for a in args {
acc = acc.add(&as_rational_function(a, var, pool)?);
}
Some(acc)
}
ExprData::Mul(args) => {
let mut acc = QRatFn::constant(Rational::from(1));
for a in args {
acc = acc.mul(&as_rational_function(a, var, pool)?);
}
Some(acc)
}
ExprData::Pow { base, exp } => {
let ei = integer_value(exp, pool)?;
let ei32 = i32::try_from(ei).ok()?;
if ei32.unsigned_abs() > 64 {
return None;
}
as_rational_function(base, var, pool)?.pow_int(ei32)
}
_ => None,
}
}
pub(crate) fn integer_value(e: ExprId, pool: &ExprPool) -> Option<i64> {
match pool.get(e) {
crate::kernel::ExprData::Integer(i) => i.0.to_i64(),
_ => None,
}
}
pub(crate) fn rational_value(e: ExprId, pool: &ExprPool) -> Option<Rational> {
match pool.get(e) {
crate::kernel::ExprData::Integer(i) => Some(Rational::from(i.0.clone())),
crate::kernel::ExprData::Rational(r) => Some(r.0.clone()),
_ => None,
}
}
pub(crate) fn split_rational_roots(p: &QPoly) -> Option<(Rational, Vec<Rational>)> {
let p = qp_trim(p.clone());
if qp_is_zero(&p) {
return None;
}
let lc = p.last()?.clone();
let deg = p.len() - 1;
if deg == 0 {
return Some((lc, Vec::new()));
}
let mut monic: QPoly = p.iter().map(|c| Rational::from(c / &lc)).collect();
let mut roots = Vec::new();
while monic.len() > 1 {
if monic[0] == 0 {
roots.push(Rational::from(0));
monic.remove(0);
continue;
}
let r = find_rational_root(&monic)?;
let n = monic.len();
let mut q = vec![Rational::from(0); n - 1];
let mut carry = Rational::from(0);
for i in (0..n - 1).rev() {
carry = &monic[i + 1] + Rational::from(&carry * &r);
q[i] = carry.clone();
}
roots.push(r);
monic = qp_trim(q);
}
Some((lc, roots))
}
const DIVISOR_SEARCH_CAP: i64 = 1 << 34;
fn find_rational_root(p: &QPoly) -> Option<Rational> {
let mut lcm = Integer::from(1);
for c in p {
lcm = lcm.lcm(c.denom());
}
let ints: Vec<Integer> = p
.iter()
.map(|c| Integer::from(&lcm / c.denom()) * c.numer())
.collect();
let a0 = ints.first()?.clone();
let an = ints.last()?.clone();
if a0 == 0 {
return Some(Rational::from(0));
}
let a0m = a0.clone().abs().to_i64()?;
let anm = an.clone().abs().to_i64()?;
if a0m > DIVISOR_SEARCH_CAP || anm > DIVISOR_SEARCH_CAP {
return None;
}
let pdiv = divisors(a0m);
let qdiv = divisors(anm);
for pn in &pdiv {
for qd in &qdiv {
for sign in [1i64, -1] {
let cand = Rational::from((sign * pn, *qd));
if qp_eval(p, &cand) == 0 {
return Some(cand);
}
}
}
}
None
}
fn divisors(n: i64) -> Vec<i64> {
let mut out = Vec::new();
let mut d = 1i64;
while d.saturating_mul(d) <= n {
if n % d == 0 {
out.push(d);
if d != n / d {
out.push(n / d);
}
}
d += 1;
}
out.sort_unstable();
out
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct C64 {
pub re: f64,
pub im: f64,
}
impl C64 {
pub fn new(re: f64, im: f64) -> Self {
C64 { re, im }
}
pub fn add(self, o: C64) -> C64 {
C64::new(self.re + o.re, self.im + o.im)
}
pub fn sub(self, o: C64) -> C64 {
C64::new(self.re - o.re, self.im - o.im)
}
pub fn mul(self, o: C64) -> C64 {
C64::new(
self.re * o.re - self.im * o.im,
self.re * o.im + self.im * o.re,
)
}
pub fn div(self, o: C64) -> C64 {
let d = o.re * o.re + o.im * o.im;
C64::new(
(self.re * o.re + self.im * o.im) / d,
(self.im * o.re - self.re * o.im) / d,
)
}
pub fn abs(self) -> f64 {
self.re.hypot(self.im)
}
}
pub(crate) fn complex_roots(coeffs: &[f64]) -> Option<Vec<C64>> {
let mut c: Vec<f64> = coeffs.to_vec();
while c.len() > 1 && c.last().is_some_and(|v| v.abs() < 1e-300) {
c.pop();
}
let deg = c.len().checked_sub(1)?;
if deg == 0 {
return Some(Vec::new());
}
let lc = *c.last()?;
if lc.abs() < 1e-300 {
return None;
}
let monic: Vec<f64> = c.iter().map(|v| v / lc).collect();
let eval = |z: C64| -> C64 {
let mut acc = C64::new(0.0, 0.0);
for &coef in monic.iter().rev() {
acc = acc.mul(z).add(C64::new(coef, 0.0));
}
acc
};
let mut z: Vec<C64> = (0..deg)
.map(|k| {
let ang = 2.0 * std::f64::consts::PI * (k as f64) / (deg as f64) + 0.4;
C64::new(0.4 + 0.9 * ang.cos(), 0.9 * ang.sin())
})
.collect();
for _ in 0..2000 {
let mut max_step = 0.0f64;
for i in 0..deg {
let mut denom = C64::new(1.0, 0.0);
for j in 0..deg {
if i != j {
denom = denom.mul(z[i].sub(z[j]));
}
}
if denom.abs() < 1e-300 {
return None;
}
let step = eval(z[i]).div(denom);
z[i] = z[i].sub(step);
max_step = max_step.max(step.abs());
}
if max_step < 1e-14 {
if z.iter()
.all(|&zi| eval(zi).abs() < 1e-8 * (1.0 + zi.abs()).powi(deg as i32))
{
return Some(z);
}
return Some(z);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bernoulli_values() {
let b = bernoulli_numbers(8);
assert_eq!(b[0], Rational::from(1));
assert_eq!(b[1], Rational::from((-1, 2)));
assert_eq!(b[2], Rational::from((1, 6)));
assert_eq!(b[3], Rational::from(0));
assert_eq!(b[4], Rational::from((-1, 30)));
assert_eq!(b[6], Rational::from((1, 42)));
assert_eq!(b[8], Rational::from((-1, 30)));
}
#[test]
fn bernoulli_poly_at_zero_is_bernoulli_number() {
let b = bernoulli_numbers(6);
for n in 0..=6 {
assert_eq!(bernoulli_poly_at(n, &Rational::from(0), &b), b[n]);
}
}
#[test]
fn bernoulli_poly_b2() {
let b = bernoulli_numbers(4);
let x = Rational::from((1, 2));
let got = bernoulli_poly_at(2, &x, &b);
let want = Rational::from((1, 4)) - Rational::from((1, 2)) + Rational::from((1, 6));
assert_eq!(got, want);
}
#[test]
fn split_roots_quadratic() {
let p = vec![Rational::from(1), Rational::from(-3), Rational::from(2)];
let (lc, mut roots) = split_rational_roots(&p).unwrap();
assert_eq!(lc, Rational::from(2));
roots.sort();
assert_eq!(roots, vec![Rational::from((1, 2)), Rational::from(1)]);
}
#[test]
fn split_roots_declines_irrational() {
let p = vec![Rational::from(-2), Rational::from(0), Rational::from(1)];
assert!(split_rational_roots(&p).is_none());
}
#[test]
fn complex_roots_of_quadratic() {
let r = complex_roots(&[1.0, 0.0, 1.0]).unwrap();
assert_eq!(r.len(), 2);
assert!(r.iter().all(|z| (z.abs() - 1.0).abs() < 1e-9));
assert!(r.iter().all(|z| z.re.abs() < 1e-9));
}
#[test]
fn gate_rejects_wrong_coefficient() {
let pts = [10.0, 100.0, 1000.0];
let oracle: Vec<f64> = pts.iter().map(|n| 1.0 + 1.0 / n).collect();
let terms = vec![
vec![1.0; 3],
pts.iter().map(|n| 2.0 / n).collect::<Vec<_>>(),
];
assert_eq!(gate_accept(&oracle, &terms, 1.5), 1);
}
#[test]
fn gate_accepts_right_coefficient() {
let pts = [10.0, 100.0, 1000.0];
let oracle: Vec<f64> = pts.iter().map(|n| 1.0 + 1.0 / n + 3.0 / (n * n)).collect();
let terms = vec![
vec![1.0; 3],
pts.iter().map(|n| 1.0 / n).collect::<Vec<_>>(),
pts.iter().map(|n| 3.0 / (n * n)).collect::<Vec<_>>(),
];
assert_eq!(gate_accept(&oracle, &terms, DEFAULT_SLACK), 3);
}
#[test]
fn as_rational_function_basic() {
let pool = ExprPool::new();
let n = pool.symbol("n", crate::kernel::Domain::Real);
let e = simplify(
pool.mul(vec![
pool.add(vec![n, pool.integer(1_i32)]),
pool.pow(
pool.add(vec![n, pool.integer(-1_i32)]),
pool.integer(-1_i32),
),
]),
&pool,
)
.value;
let r = as_rational_function(e, n, &pool).unwrap();
let v = qp_eval(&r.num, &Rational::from(3)) / qp_eval(&r.den, &Rational::from(3));
assert_eq!(v, Rational::from(2));
}
}