use crate::kernel::{ExprData, ExprId, ExprPool};
use rug::ops::Pow;
use rug::{Integer, Rational};
use std::collections::HashMap;
#[derive(Clone, Debug)]
struct Tps {
v: i64,
c: Vec<Rational>,
prec: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Fail {
Unsupported,
Precision,
}
type R<T> = Result<T, Fail>;
impl Tps {
fn new(v: i64, mut c: Vec<Rational>, prec: i64) -> Tps {
let len = (prec - v).max(0) as usize;
c.truncate(len);
c.resize(len, Rational::new());
let lead = c.iter().position(|x| *x != 0);
match lead {
None => Tps {
v: prec,
c: Vec::new(),
prec,
},
Some(i) => {
c.drain(..i);
Tps {
v: v + i as i64,
c,
prec,
}
}
}
}
fn constant(r: Rational, p: i64) -> Tps {
Tps::new(0, vec![r], p)
}
fn monomial(k: i64, p: i64) -> Tps {
Tps::new(k, vec![Rational::from(1)], p)
}
fn is_zero(&self) -> bool {
self.c.is_empty()
}
fn coef(&self, e: i64) -> Rational {
debug_assert!(e < self.prec);
if e < self.v {
Rational::new()
} else {
self.c[(e - self.v) as usize].clone()
}
}
fn dense_from_zero(&self, n: usize) -> Vec<Rational> {
(0..n as i64).map(|e| self.coef(e)).collect()
}
}
fn checkpoint(k: usize) -> R<()> {
if k % 16 == 15 && crate::budget::check().is_err() {
return Err(Fail::Unsupported);
}
Ok(())
}
fn dmul(a: &[Rational], b: &[Rational], n: usize) -> R<Vec<Rational>> {
let mut out = vec![Rational::new(); n];
for (i, ai) in a.iter().enumerate().take(n) {
checkpoint(i)?;
if *ai == 0 {
continue;
}
for (j, bj) in b.iter().enumerate().take(n - i) {
if *bj == 0 {
continue;
}
out[i + j] += Rational::from(ai * bj);
}
}
Ok(out)
}
fn dinv(a: &[Rational], n: usize) -> R<Vec<Rational>> {
let inv0 = Rational::from(1) / &a[0];
let mut d: Vec<Rational> = Vec::with_capacity(n);
for k in 0..n {
checkpoint(k)?;
if k == 0 {
d.push(inv0.clone());
continue;
}
let mut s = Rational::new();
for j in 1..=k.min(a.len().saturating_sub(1)) {
if a[j] != 0 {
s += Rational::from(&a[j] * &d[k - j]);
}
}
d.push(-(s * &inv0));
}
Ok(d)
}
fn dpow(a: &[Rational], alpha: &Rational, w0: Rational, n: usize) -> R<Vec<Rational>> {
let mut w: Vec<Rational> = Vec::with_capacity(n);
if n == 0 {
return Ok(w);
}
w.push(w0);
let a0 = &a[0];
let alpha1 = Rational::from(alpha + 1u32);
for k in 1..n {
checkpoint(k)?;
let mut s = Rational::new();
for j in 1..=k.min(a.len().saturating_sub(1)) {
if a[j] == 0 {
continue;
}
let f = Rational::from(&alpha1 * j as u64) - k as u64;
if f == 0 {
continue;
}
s += f * Rational::from(&a[j] * &w[k - j]);
}
w.push(s / Rational::from(a0 * k as u64));
}
Ok(w)
}
fn dexp(g: &[Rational], n: usize) -> R<Vec<Rational>> {
let mut e: Vec<Rational> = Vec::with_capacity(n);
if n == 0 {
return Ok(e);
}
e.push(Rational::from(1));
for k in 1..n {
checkpoint(k)?;
let mut s = Rational::new();
for j in 1..=k.min(g.len().saturating_sub(1)) {
if g[j] != 0 {
s += Rational::from(&g[j] * &e[k - j]) * j as u64;
}
}
e.push(s / k as u64);
}
Ok(e)
}
fn dlog(g: &[Rational], n: usize) -> R<Vec<Rational>> {
let mut l: Vec<Rational> = Vec::with_capacity(n);
if n == 0 {
return Ok(l);
}
l.push(Rational::new());
for k in 1..n {
checkpoint(k)?;
let mut s = if k < g.len() {
Rational::from(&g[k] * k as u64)
} else {
Rational::new()
};
for j in 1..k {
if k - j < g.len() && g[k - j] != 0 && l[j] != 0 {
s -= Rational::from(&l[j] * &g[k - j]) * j as u64;
}
}
l.push(s / k as u64);
}
Ok(l)
}
fn dsincos(g: &[Rational], n: usize, hyperbolic: bool) -> R<(Vec<Rational>, Vec<Rational>)> {
let mut s: Vec<Rational> = Vec::with_capacity(n);
let mut c: Vec<Rational> = Vec::with_capacity(n);
if n == 0 {
return Ok((s, c));
}
s.push(Rational::new());
c.push(Rational::from(1));
for k in 1..n {
checkpoint(k)?;
let mut ss = Rational::new();
let mut cs = Rational::new();
for j in 1..=k.min(g.len().saturating_sub(1)) {
if g[j] == 0 {
continue;
}
let jg = Rational::from(&g[j] * j as u64);
ss += Rational::from(&jg * &c[k - j]);
cs += jg * &s[k - j];
}
s.push(ss / k as u64);
let ck = cs / k as u64;
c.push(if hyperbolic { ck } else { -ck });
}
Ok((s, c))
}
fn dderiv(g: &[Rational], n: usize) -> Vec<Rational> {
(1..n)
.map(|k| {
if k < g.len() {
Rational::from(&g[k] * k as u64)
} else {
Rational::new()
}
})
.collect()
}
fn dinteg(h: &[Rational], n: usize) -> Vec<Rational> {
let mut out = Vec::with_capacity(n);
if n == 0 {
return out;
}
out.push(Rational::new());
for k in 1..n {
out.push(if k - 1 < h.len() {
Rational::from(&h[k - 1] / k as u64)
} else {
Rational::new()
});
}
out
}
fn rational_power(r: &Rational, alpha: &Rational) -> Option<Rational> {
let (p, q) = (alpha.numer(), alpha.denom());
let q = q.to_u32()?;
let p = p.to_i32()?;
let base = if q == 1 {
r.clone()
} else {
if *r <= 0 {
return None;
}
let root = |z: &Integer| -> Option<Integer> {
let t = Integer::from(z.root_ref(q));
if t.clone().pow(q) == *z {
Some(t)
} else {
None
}
};
Rational::from((root(r.numer())?, root(r.denom())?))
};
if p < 0 && base == 0 {
return None;
}
Some(base.pow(p))
}
const MAX_COEFFICIENTS: i64 = 1 << 15;
fn coefficient_count(v: i64, prec: i64) -> R<usize> {
match prec.checked_sub(v) {
Some(n) if (0..=MAX_COEFFICIENTS).contains(&n) => Ok(n as usize),
_ => Err(Fail::Unsupported),
}
}
fn literal(id: ExprId, pool: &ExprPool) -> Option<Rational> {
pool.with(id, |d| match d {
ExprData::Integer(n) => Some(Rational::from(&n.0)),
ExprData::Rational(r) => Some(r.0.clone()),
_ => None,
})
}
struct Ctx<'a> {
xi: ExprId,
p: i64,
memo: HashMap<ExprId, Tps>,
pool: &'a ExprPool,
}
impl Ctx<'_> {
fn add(&self, a: &Tps, b: &Tps) -> Tps {
let prec = a.prec.min(b.prec);
let v = a.v.min(b.v).min(prec);
let c = (v..prec).map(|e| a.coef(e) + b.coef(e)).collect();
Tps::new(v, c, prec)
}
fn mul(&self, a: &Tps, b: &Tps) -> R<Tps> {
let prec = (a.prec + b.v).min(b.prec + a.v).min(self.p);
let v = a.v + b.v;
if a.is_zero() || b.is_zero() || prec <= v {
return Ok(Tps::new(prec, Vec::new(), prec));
}
let n = coefficient_count(v, prec)?;
Ok(Tps::new(v, dmul(&a.c, &b.c, n)?, prec))
}
fn pow(&self, a: &Tps, alpha: &Rational) -> R<Tps> {
if *alpha == 0 {
return Err(Fail::Unsupported);
}
if a.is_zero() {
if alpha.denom() == &1 && *alpha > 0 && a.prec >= 1 {
let n = alpha.numer().to_i64().ok_or(Fail::Unsupported)?;
let prec = a.prec.saturating_mul(n).min(self.p);
return Ok(Tps::new(prec, Vec::new(), prec));
}
return Err(Fail::Precision);
}
let integral = alpha.denom() == &1;
if !integral && a.v != 0 {
return Err(Fail::Unsupported);
}
let v = if integral {
let n = alpha.numer().to_i64().ok_or(Fail::Unsupported)?;
a.v.checked_mul(n).ok_or(Fail::Unsupported)?
} else {
0
};
let w0 = rational_power(&a.c[0], alpha).ok_or(Fail::Unsupported)?;
let prec = v
.checked_add(a.c.len() as i64)
.ok_or(Fail::Unsupported)?
.min(self.p);
if prec <= v {
return Ok(Tps::new(prec, Vec::new(), prec));
}
let n = coefficient_count(v, prec)?;
Ok(Tps::new(v, dpow(&a.c, alpha, w0, n)?, prec))
}
fn zero_constant_arg(&self, g: &Tps) -> R<(Vec<Rational>, usize)> {
if g.prec <= 0 {
return Err(Fail::Precision);
}
if !g.is_zero() && g.v < 1 {
return Err(Fail::Unsupported);
}
let n = g.prec.min(self.p) as usize;
Ok((g.dense_from_zero(n), n))
}
fn unit_constant_arg(&self, g: &Tps) -> R<(Vec<Rational>, usize)> {
if g.prec <= 0 {
return Err(Fail::Precision);
}
if g.is_zero() || g.v != 0 || g.c[0] != 1 {
return Err(Fail::Unsupported);
}
let n = g.prec.min(self.p) as usize;
Ok((g.dense_from_zero(n), n))
}
fn func(&self, name: &str, g: &Tps) -> R<Tps> {
let dense = |c: Vec<Rational>, n: usize| Tps::new(0, c, n as i64);
match name {
"exp" => {
let (gd, n) = self.zero_constant_arg(g)?;
Ok(dense(dexp(&gd, n)?, n))
}
"log" => {
let (gd, n) = self.unit_constant_arg(g)?;
Ok(dense(dlog(&gd, n)?, n))
}
"sin" | "cos" | "sinh" | "cosh" | "tan" | "tanh" => {
let (gd, n) = self.zero_constant_arg(g)?;
let hyp = name.ends_with('h');
let (s, c) = dsincos(&gd, n, hyp)?;
Ok(match name {
"sin" | "sinh" => dense(s, n),
"cos" | "cosh" => dense(c, n),
_ => dense(dmul(&s, &dinv(&c, n)?, n)?, n),
})
}
"atan" | "atanh" | "asin" | "asinh" => {
let (gd, n) = self.zero_constant_arg(g)?;
let m = n.saturating_sub(1);
let g2 = dmul(&gd, &gd, m)?;
let sign_plus = matches!(name, "atan" | "asinh");
let one_pm: Vec<Rational> = g2
.into_iter()
.enumerate()
.map(|(i, x)| {
let x = if sign_plus { x } else { -x };
if i == 0 {
x + 1u32
} else {
x
}
})
.collect();
let factor = if m == 0 {
Vec::new()
} else if name.starts_with("at") {
dinv(&one_pm, m)?
} else {
let alpha = Rational::from((-1, 2));
dpow(&one_pm, &alpha, Rational::from(1), m)?
};
let h = dmul(&dderiv(&gd, n), &factor, m)?;
Ok(dense(dinteg(&h, n), n))
}
"sqrt" => self.pow(g, &Rational::from((1, 2))),
_ => Err(Fail::Unsupported),
}
}
fn eval(&mut self, e: ExprId) -> R<Tps> {
if let Some(t) = self.memo.get(&e) {
return Ok(t.clone());
}
if crate::budget::check().is_err() {
return Err(Fail::Unsupported);
}
let pool = self.pool;
let out = match pool.get(e) {
ExprData::Integer(n) => Tps::constant(Rational::from(&n.0), self.p),
ExprData::Rational(r) => Tps::constant(r.0, self.p),
ExprData::Symbol { .. } if e == self.xi => Tps::monomial(1, self.p),
ExprData::Add(xs) => {
let mut acc: Option<Tps> = None;
for x in xs {
let t = self.eval(x)?;
acc = Some(match acc {
None => t,
Some(a) => self.add(&a, &t),
});
}
acc.unwrap_or_else(|| Tps::constant(Rational::new(), self.p))
}
ExprData::Mul(xs) => {
let mut acc: Option<Tps> = None;
for x in xs {
let t = self.eval(x)?;
acc = Some(match acc {
None => t,
Some(a) => self.mul(&a, &t)?,
});
}
acc.unwrap_or_else(|| Tps::constant(Rational::from(1), self.p))
}
ExprData::Pow { base, exp } => {
let alpha = literal(exp, pool).ok_or(Fail::Unsupported)?;
if base == self.xi && alpha.denom() == &1 {
let k = alpha.numer().to_i64().ok_or(Fail::Unsupported)?;
if k == 0 {
return Err(Fail::Unsupported);
}
if k < 0 {
coefficient_count(k, self.p)?;
}
Tps::monomial(k, self.p)
} else {
let b = self.eval(base)?;
self.pow(&b, &alpha)?
}
}
ExprData::Func { name, args } if args.len() == 1 => {
let g = self.eval(args[0])?;
self.func(&name, &g)?
}
_ => return Err(Fail::Unsupported),
};
self.memo.insert(e, out.clone());
Ok(out)
}
}
const MAX_TPS_ORDER: u32 = 4096;
const MAX_EXTRA_PRECISION: i64 = 128;
pub(crate) fn tps_expansion(
shifted: ExprId,
xi: ExprId,
order: u32,
pool: &ExprPool,
) -> Option<(i32, Vec<Rational>)> {
if order == 0 || order > MAX_TPS_ORDER {
return None;
}
let order = i64::from(order);
let p_max = 2 * order + MAX_EXTRA_PRECISION;
let mut p = order;
loop {
let mut ctx = Ctx {
xi,
p,
memo: HashMap::new(),
pool,
};
let deficit = match ctx.eval(shifted) {
Err(Fail::Unsupported) => return None,
Err(Fail::Precision) => (p / 2).max(8),
Ok(r) => {
let target = if r.is_zero() || r.v >= 0 {
order
} else {
r.v + order
};
if r.prec >= target {
return shape(&r, order);
}
target - r.prec
}
};
p += deficit;
if p > p_max {
return None;
}
}
}
fn shape(r: &Tps, order: i64) -> Option<(i32, Vec<Rational>)> {
let start = if !r.is_zero() && r.v < 0 { r.v } else { 0 };
let valuation = i32::try_from(start).ok()?;
let coeffs = (start..start + order).map(|e| r.coef(e)).collect();
Some((valuation, coeffs))
}
pub(crate) fn rational_literal(r: &Rational, pool: &ExprPool) -> ExprId {
if *r.denom() == 1 {
pool.integer(r.numer().clone())
} else {
pool.rational(r.numer().clone(), r.denom().clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::Domain;
fn q(n: i64, d: i64) -> Rational {
Rational::from((n, d))
}
fn expand(e: ExprId, x: ExprId, order: u32, p: &ExprPool) -> Option<(i32, Vec<Rational>)> {
tps_expansion(e, x, order, p)
}
#[test]
fn sin_and_exp_coefficients() {
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let (v, c) = expand(p.func("sin", vec![x]), x, 8, &p).unwrap();
assert_eq!(v, 0);
assert_eq!(
c,
vec![
q(0, 1),
q(1, 1),
q(0, 1),
q(-1, 6),
q(0, 1),
q(1, 120),
q(0, 1),
q(-1, 5040)
]
);
let (_, c) = expand(p.func("exp", vec![x]), x, 5, &p).unwrap();
assert_eq!(c, vec![q(1, 1), q(1, 1), q(1, 2), q(1, 6), q(1, 24)]);
}
#[test]
fn a_deep_cancellation_raises_the_working_precision() {
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let st = p.func("sin", vec![p.func("tan", vec![x])]);
let ts = p.func("tan", vec![p.func("sin", vec![x])]);
let num = p.add(vec![st, p.mul(vec![p.integer(-1), ts])]);
let e = p.mul(vec![num, p.pow(x, p.integer(-7))]);
let (v, c) = expand(e, x, 3, &p).unwrap();
assert_eq!(v, 0);
assert_eq!(c, vec![q(-1, 30), q(0, 1), q(-29, 756)]);
}
#[test]
fn a_pole_reports_its_valuation() {
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let e = p.pow(p.func("sin", vec![x]), p.integer(-2));
let (v, c) = expand(e, x, 3, &p).unwrap();
assert_eq!(v, -2);
assert_eq!(c, vec![q(1, 1), q(0, 1), q(1, 3)]);
}
#[test]
fn irrational_or_singular_inputs_are_declined() {
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let a = p.symbol("a", Domain::Real);
let one_plus_x = p.add(vec![p.integer(1), x]);
let two_plus_x = p.add(vec![p.integer(2), x]);
for e in [
p.func("sin", vec![one_plus_x]),
p.func("log", vec![two_plus_x]),
p.func("sqrt", vec![two_plus_x]),
p.func("sqrt", vec![x]),
p.func("log", vec![x]),
p.func("exp", vec![p.pow(x, p.integer(-1))]),
p.mul(vec![a, x]),
p.func("acos", vec![x]),
p.pow(p.pow(x, p.integer(2)), p.rational(1, 2)),
] {
assert!(expand(e, x, 4, &p).is_none(), "{}", p.display(e));
}
let four_plus_x = p.add(vec![p.integer(4), x]);
let (_, c) = expand(p.func("sqrt", vec![four_plus_x]), x, 3, &p).unwrap();
assert_eq!(c, vec![q(2, 1), q(1, 4), q(-1, 64)]);
}
#[test]
fn a_huge_pole_is_declined_not_allocated() {
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let e = p.pow(x, p.integer(-1_000_000_000_i64));
assert!(expand(e, x, 4, &p).is_none());
let e = p.pow(p.func("sin", vec![x]), p.integer(-1_000_000_000_i64));
let (v, c) = expand(e, x, 3, &p).unwrap();
assert_eq!(v, -1_000_000_000);
assert_eq!(c, vec![q(1, 1), q(0, 1), q(500_000_000, 3)]);
let e = p.pow(p.func("sin", vec![x]), p.integer(-3_000_000_000_i64));
assert!(expand(e, x, 3, &p).is_none());
}
#[test]
fn an_exhausted_budget_declines() {
use crate::budget::{self, Budget};
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let e = p.func("exp", vec![p.func("sin", vec![x])]);
let _guard = budget::enter(Budget::new().with_max_steps(1));
assert!(expand(e, x, 400, &p).is_none());
}
#[test]
fn a_divisor_that_is_zero_to_every_precision_is_declined() {
let p = ExprPool::new();
let x = p.symbol("x", Domain::Real);
let s2 = p.pow(p.func("sin", vec![x]), p.integer(2));
let c2 = p.pow(p.func("cos", vec![x]), p.integer(2));
let z = p.add(vec![s2, c2, p.integer(-1)]);
let (v, c) = expand(z, x, 4, &p).unwrap();
assert_eq!(v, 0);
assert!(c.iter().all(|r| *r == 0));
assert!(expand(p.pow(z, p.integer(-1)), x, 4, &p).is_none());
}
}