use crate::calculus::series::{local_expansion, SeriesError};
use crate::kernel::{ExprData, ExprId, ExprPool};
use rug::{Integer, Rational};
use std::cell::RefCell;
use std::rc::Rc;
type Gen<'p> = dyn Fn(usize, &[Rational]) -> Rational + 'p;
#[derive(Clone)]
pub struct Fps<'p> {
gen: Rc<Gen<'p>>,
cache: Rc<RefCell<Vec<Rational>>>,
}
impl std::fmt::Debug for Fps<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Fps")
.field("computed", &self.cache.borrow().len())
.finish_non_exhaustive()
}
}
impl<'p> Fps<'p> {
fn from_gen<F>(gen: F) -> Self
where
F: Fn(usize, &[Rational]) -> Rational + 'p,
{
Fps {
gen: Rc::new(gen),
cache: Rc::new(RefCell::new(Vec::new())),
}
}
pub fn from_fn<F>(f: F) -> Self
where
F: Fn(usize) -> Rational + 'p,
{
Fps::from_gen(move |n, _| f(n))
}
pub fn from_poly(coeffs: &[Rational]) -> Self {
let coeffs: Vec<Rational> = coeffs.to_vec();
Fps::from_fn(move |n| coeffs.get(n).cloned().unwrap_or_else(|| Rational::from(0)))
}
pub fn zero() -> Self {
Fps::from_fn(|_| Rational::from(0))
}
pub fn constant(c: Rational) -> Self {
Fps::from_fn(move |n| if n == 0 { c.clone() } else { Rational::from(0) })
}
pub fn x() -> Self {
Fps::from_fn(|n| {
if n == 1 {
Rational::from(1)
} else {
Rational::from(0)
}
})
}
pub fn coeff(&self, n: usize) -> Rational {
{
let cache = self.cache.borrow();
if n < cache.len() {
return cache[n].clone();
}
}
let mut next = self.cache.borrow().len();
while next <= n {
let prev: Vec<Rational> = self.cache.borrow().clone();
let c = (self.gen)(next, &prev);
let mut cache = self.cache.borrow_mut();
if cache.len() == next {
cache.push(c);
}
next = cache.len();
}
self.cache.borrow()[n].clone()
}
pub fn coeffs(&self, n: usize) -> Vec<Rational> {
(0..n).map(|i| self.coeff(i)).collect()
}
pub fn from_rational(num: &[Rational], den: &[Rational]) -> Result<Self, FpsError> {
let q0 = den.first().cloned().unwrap_or_else(|| Rational::from(0));
if q0 == 0 {
return Err(FpsError::DenominatorVanishesAtZero);
}
let num: Vec<Rational> = num.to_vec();
let den: Vec<Rational> = den.to_vec();
Ok(Fps::from_gen(move |n, prev| {
let pn = num.get(n).cloned().unwrap_or_else(|| Rational::from(0));
let mut acc = pn;
for k in 1..=n {
if let Some(qk) = den.get(k) {
if *qk != 0 {
acc -= qk.clone() * prev[n - k].clone();
}
}
}
acc / q0.clone()
}))
}
pub fn from_expr(expr: ExprId, var: ExprId, pool: &'p ExprPool) -> Result<Self, FpsError> {
probe_expr_coeffs(expr, var, 1, pool)?;
let expanded: Rc<RefCell<Vec<Rational>>> = Rc::new(RefCell::new(Vec::new()));
Ok(Fps::from_gen(move |n, _prev| {
{
let e = expanded.borrow();
if n < e.len() {
return e[n].clone();
}
}
let mut order = (n + 1).max(4);
let have = expanded.borrow().len();
if order < have * 2 {
order = have * 2;
}
let coeffs = probe_expr_coeffs(expr, var, order as u32, pool)
.unwrap_or_else(|_| vec![Rational::from(0); order]);
let mut e = expanded.borrow_mut();
*e = coeffs;
e.get(n).cloned().unwrap_or_else(|| Rational::from(0))
}))
}
pub fn add(&self, other: &Fps<'p>) -> Fps<'p> {
let a = self.clone();
let b = other.clone();
Fps::from_fn(move |n| a.coeff(n) + b.coeff(n))
}
pub fn sub(&self, other: &Fps<'p>) -> Fps<'p> {
let a = self.clone();
let b = other.clone();
Fps::from_fn(move |n| a.coeff(n) - b.coeff(n))
}
pub fn mul(&self, other: &Fps<'p>) -> Fps<'p> {
let a = self.clone();
let b = other.clone();
Fps::from_fn(move |n| {
let mut acc = Rational::from(0);
for k in 0..=n {
acc += a.coeff(k) * b.coeff(n - k);
}
acc
})
}
pub fn scale(&self, c: Rational) -> Fps<'p> {
let a = self.clone();
Fps::from_fn(move |n| c.clone() * a.coeff(n))
}
pub fn derivative(&self) -> Fps<'p> {
let a = self.clone();
Fps::from_fn(move |n| Rational::from(n + 1) * a.coeff(n + 1))
}
pub fn integral(&self) -> Fps<'p> {
let a = self.clone();
Fps::from_fn(move |n| {
if n == 0 {
Rational::from(0)
} else {
a.coeff(n - 1) / Rational::from(n)
}
})
}
pub fn shift_up(&self, k: usize) -> Fps<'p> {
let a = self.clone();
Fps::from_fn(move |n| {
if n < k {
Rational::from(0)
} else {
a.coeff(n - k)
}
})
}
pub fn to_expr(&self, var: ExprId, order: u32, pool: &ExprPool) -> ExprId {
let mut terms = Vec::new();
for k in 0..order as usize {
let c = self.coeff(k);
if c == 0 {
continue;
}
let coeff_e = rat_to_expr(&c, pool);
let term = if k == 0 {
coeff_e
} else if k == 1 {
pool.mul(vec![coeff_e, var])
} else {
let p = pool.pow(var, pool.integer(k as i64));
pool.mul(vec![coeff_e, p])
};
terms.push(term);
}
let o_term = pool.big_o(pool.pow(var, pool.integer(order as i64)));
terms.push(o_term);
pool.add(terms)
}
pub fn inverse(&self) -> Result<Fps<'p>, FpsError> {
let a0 = self.coeff(0);
if a0 == 0 {
return Err(FpsError::ConstantTermMustBeNonzero);
}
let a = self.clone();
Ok(Fps::from_gen(move |n, prev| {
if n == 0 {
Rational::from(1) / a.coeff(0)
} else {
let mut acc = Rational::from(0);
for k in 1..=n {
acc += a.coeff(k) * prev[n - k].clone();
}
-acc / a.coeff(0)
}
}))
}
pub fn div(&self, other: &Fps<'p>) -> Result<Fps<'p>, FpsError> {
Ok(self.mul(&other.inverse()?))
}
pub fn compose(&self, g: &Fps<'p>) -> Result<Fps<'p>, FpsError> {
if g.coeff(0) != 0 {
return Err(FpsError::ConstantTermMustBeZero);
}
let f = self.clone();
let g = g.clone();
Ok(Fps::from_fn(move |n| {
if n == 0 {
return f.coeff(0);
}
let mut acc = Rational::from(0);
let mut pow: Vec<Rational> = vec![Rational::from(0); n + 1];
pow[0] = Rational::from(1); let gc: Vec<Rational> = (0..=n).map(|i| g.coeff(i)).collect();
for k in 1..=n {
let mut next = vec![Rational::from(0); n + 1];
for (i, pi) in pow.iter().enumerate() {
if *pi == 0 {
continue;
}
for j in 0..=(n - i) {
if gc[j] == 0 {
continue;
}
next[i + j] += pi.clone() * gc[j].clone();
}
}
pow = next;
acc += f.coeff(k) * pow[n].clone();
}
acc
}))
}
pub fn revert(&self) -> Result<Fps<'p>, FpsError> {
if self.coeff(0) != 0 {
return Err(FpsError::ConstantTermMustBeZero);
}
if self.coeff(1) == 0 {
return Err(FpsError::ConstantTermMustBeNonzero);
}
let f = self.clone();
let f_over_x = Fps::from_fn(move |n| f.coeff(n + 1));
let phi = f_over_x.inverse()?; Ok(Fps::from_fn(move |n| {
if n == 0 {
return Rational::from(0);
}
let m = n; let target = n - 1;
let phic: Vec<Rational> = (0..=target).map(|i| phi.coeff(i)).collect();
let mut pow: Vec<Rational> = vec![Rational::from(0); target + 1];
pow[0] = Rational::from(1);
for _ in 0..m {
let mut next = vec![Rational::from(0); target + 1];
for (i, pi) in pow.iter().enumerate() {
if *pi == 0 {
continue;
}
for j in 0..=(target - i) {
if phic[j] == 0 {
continue;
}
next[i + j] += pi.clone() * phic[j].clone();
}
}
pow = next;
}
pow[target].clone() / Rational::from(n)
}))
}
pub fn exp(&self) -> Result<Fps<'p>, FpsError> {
if self.coeff(0) != 0 {
return Err(FpsError::ConstantTermMustBeZero);
}
let f = self.clone();
Ok(Fps::from_gen(move |n, prev| {
if n == 0 {
return Rational::from(1);
}
let mut acc = Rational::from(0);
for k in 1..=n {
acc += Rational::from(k) * f.coeff(k) * prev[n - k].clone();
}
acc / Rational::from(n)
}))
}
pub fn log(&self) -> Result<Fps<'p>, FpsError> {
if self.coeff(0) != 1 {
return Err(FpsError::ConstantTermMustBeOne);
}
let f = self.clone();
Ok(Fps::from_gen(move |n, prev| {
if n == 0 {
return Rational::from(0);
}
let mut acc = Rational::from(n) * f.coeff(n);
for (k, bk) in prev.iter().enumerate().take(n).skip(1) {
acc -= Rational::from(k) * bk.clone() * f.coeff(n - k);
}
acc / Rational::from(n)
}))
}
pub fn pow_binomial(&self, alpha: Rational) -> Result<Fps<'p>, FpsError> {
if self.coeff(0) != 0 {
return Err(FpsError::ConstantTermMustBeZero);
}
let f = self.clone();
let u = Fps::from_fn(move |n| {
if n == 0 {
Rational::from(1)
} else {
f.coeff(n)
}
});
Ok(Fps::from_gen(move |n, prev| {
if n == 0 {
return Rational::from(1);
}
let mut acc = Rational::from(0);
for k in 1..=n {
let uk = u.coeff(k);
if uk == 0 {
continue;
}
let factor = alpha.clone() * Rational::from(k) - Rational::from(n - k);
acc += factor * uk * prev[n - k].clone();
}
acc / Rational::from(n)
}))
}
pub fn nth_root(&self, m: u32) -> Result<Fps<'p>, FpsError> {
if m == 0 {
return Err(FpsError::ConstantTermMustBeOne);
}
if self.coeff(0) != 1 {
return Err(FpsError::ConstantTermMustBeOne);
}
let one = Fps::constant(Rational::from(1));
let g = self.sub(&one); g.pow_binomial(Rational::from((1, m as i64)))
}
pub fn exp_series() -> Fps<'static> {
Fps::from_gen(|n, prev| {
if n == 0 {
Rational::from(1)
} else {
prev[n - 1].clone() / Rational::from(n)
}
})
}
pub fn log1p_series() -> Fps<'static> {
Fps::from_fn(|n| {
if n == 0 {
Rational::from(0)
} else {
let sign = if n % 2 == 1 { 1 } else { -1 };
Rational::from((sign, n as i64))
}
})
}
pub fn sin_series() -> Fps<'static> {
Fps::from_fn(|n| {
if n % 2 == 0 {
Rational::from(0)
} else {
let k = (n - 1) / 2;
let mut fact = Integer::from(1);
for i in 2..=n {
fact *= i as i64;
}
let sign = if k % 2 == 0 { 1 } else { -1 };
Rational::from((Integer::from(sign), fact))
}
})
}
pub fn cos_series() -> Fps<'static> {
Fps::from_fn(|n| {
if n % 2 == 1 {
Rational::from(0)
} else {
let k = n / 2;
let mut fact = Integer::from(1);
for i in 2..=n {
fact *= i as i64;
}
let sign = if k % 2 == 0 { 1 } else { -1 };
Rational::from((Integer::from(sign), fact))
}
})
}
pub fn atan_series() -> Fps<'static> {
Fps::from_fn(|n| {
if n % 2 == 0 {
Rational::from(0)
} else {
let k = (n - 1) / 2;
let sign = if k % 2 == 0 { 1 } else { -1 };
Rational::from((sign, n as i64))
}
})
}
pub fn binomial_series(alpha: Rational) -> Fps<'static> {
Fps::from_gen(move |n, prev| {
if n == 0 {
Rational::from(1)
} else {
let prev_c = prev[n - 1].clone();
prev_c * (alpha.clone() - Rational::from(n - 1)) / Rational::from(n)
}
})
}
pub fn implicit<F>(step: F) -> Self
where
F: Fn(usize, &[Rational]) -> Rational + 'p,
{
Fps::from_gen(step)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FpsError {
DenominatorVanishesAtZero,
NotAnalyticAtZero,
NonRationalCoefficient,
ConstantTermMustBeZero,
ConstantTermMustBeOne,
ConstantTermMustBeNonzero,
Series(String),
}
impl std::fmt::Display for FpsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FpsError::DenominatorVanishesAtZero => {
write!(f, "rational-function denominator vanishes at x = 0")
}
FpsError::NotAnalyticAtZero => write!(f, "expression is not analytic at x = 0"),
FpsError::NonRationalCoefficient => {
write!(f, "series coefficient is not a rational number")
}
FpsError::ConstantTermMustBeZero => write!(f, "operation requires f(0) = 0"),
FpsError::ConstantTermMustBeOne => write!(f, "operation requires f(0) = 1"),
FpsError::ConstantTermMustBeNonzero => write!(f, "operation requires f(0) != 0"),
FpsError::Series(e) => write!(f, "series expansion failed: {e}"),
}
}
}
impl std::error::Error for FpsError {}
impl crate::errors::AlkahestError for FpsError {
fn code(&self) -> &'static str {
match self {
FpsError::DenominatorVanishesAtZero => "E-FPS-001",
FpsError::NotAnalyticAtZero => "E-FPS-002",
FpsError::NonRationalCoefficient => "E-FPS-003",
FpsError::ConstantTermMustBeZero => "E-FPS-004",
FpsError::ConstantTermMustBeOne => "E-FPS-005",
FpsError::ConstantTermMustBeNonzero => "E-FPS-006",
FpsError::Series(_) => "E-FPS-007",
}
}
}
impl From<SeriesError> for FpsError {
fn from(e: SeriesError) -> Self {
FpsError::Series(e.to_string())
}
}
fn probe_expr_coeffs(
expr: ExprId,
var: ExprId,
order: u32,
pool: &ExprPool,
) -> Result<Vec<Rational>, FpsError> {
let zero = pool.integer(0);
let le = local_expansion(expr, var, zero, order, pool)?;
if le.valuation < 0 {
return Err(FpsError::NotAnalyticAtZero);
}
let shift = le.valuation as usize;
let mut out = vec![Rational::from(0); order as usize];
for (i, &c) in le.coeffs.iter().enumerate() {
let idx = shift + i;
if idx >= order as usize {
break;
}
let cs = crate::simplify::simplify(c, pool).value;
let r = expr_to_rational(cs, pool).ok_or(FpsError::NonRationalCoefficient)?;
out[idx] = r;
}
Ok(out)
}
fn expr_to_rational(e: ExprId, pool: &ExprPool) -> Option<Rational> {
match pool.get(e) {
ExprData::Integer(ref n) => Some(Rational::from((n.0.clone(), Integer::from(1)))),
ExprData::Rational(ref r) => Some(r.0.clone()),
ExprData::Add(ref args) => {
let mut acc = Rational::from(0);
for &a in args {
acc += expr_to_rational(a, pool)?;
}
Some(acc)
}
ExprData::Mul(ref args) => {
let mut acc = Rational::from(1);
for &a in args {
acc *= expr_to_rational(a, pool)?;
}
Some(acc)
}
ExprData::Pow { base, exp } => match pool.get(exp) {
ExprData::Integer(ref n) => {
let ei = n.0.to_i32()?;
let b = expr_to_rational(base, pool)?;
if ei == 0 {
Some(Rational::from(1))
} else if ei > 0 {
let mut acc = Rational::from(1);
for _ in 0..ei {
acc *= b.clone();
}
Some(acc)
} else {
if b == 0 {
return None;
}
let mut acc = Rational::from(1);
for _ in 0..(-ei) {
acc *= b.clone();
}
Some(Rational::from(1) / acc)
}
}
_ => None,
},
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let a = expr_to_rational(args[0], pool)?;
let name = name.as_str();
if a == 0 {
match name {
"exp" | "cos" | "cosh" => Some(Rational::from(1)),
"sin" | "sinh" | "tan" | "tanh" | "atan" | "asin" | "asinh" => {
Some(Rational::from(0))
}
_ => None,
}
} else if a == 1 && (name == "log" || name == "ln") {
Some(Rational::from(0))
} else {
None
}
}
_ => None,
}
}
fn rat_to_expr(r: &Rational, pool: &ExprPool) -> ExprId {
let (num, den) = (r.numer().clone(), r.denom().clone());
if den == 1 {
pool.integer(num)
} else {
pool.rational(num, den)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::Domain;
use rug::ops::Pow;
fn r(n: i64, d: i64) -> Rational {
Rational::from((n, d))
}
fn ri(n: i64) -> Rational {
Rational::from(n)
}
fn factorial(n: u64) -> Integer {
let mut f = Integer::from(1);
for i in 2..=n {
f *= i;
}
f
}
#[test]
fn exp_coeffs_match_one_over_factorial() {
let e = Fps::exp_series();
for n in 0..15u64 {
assert_eq!(
e.coeff(n as usize),
Rational::from((Integer::from(1), factorial(n)))
);
}
}
#[test]
fn log1p_coeffs() {
let l = Fps::log1p_series();
assert_eq!(l.coeff(0), ri(0));
assert_eq!(l.coeff(1), ri(1));
assert_eq!(l.coeff(2), r(-1, 2));
assert_eq!(l.coeff(3), r(1, 3));
assert_eq!(l.coeff(4), r(-1, 4));
}
#[test]
fn sin_cos_coeffs() {
let s = Fps::sin_series();
assert_eq!(s.coeff(1), ri(1));
assert_eq!(s.coeff(3), r(-1, 6));
assert_eq!(s.coeff(5), r(1, 120));
assert_eq!(s.coeff(2), ri(0));
let c = Fps::cos_series();
assert_eq!(c.coeff(0), ri(1));
assert_eq!(c.coeff(2), r(-1, 2));
assert_eq!(c.coeff(4), r(1, 24));
assert_eq!(c.coeff(1), ri(0));
}
#[test]
fn binomial_series_half() {
let b = Fps::binomial_series(r(1, 2));
assert_eq!(b.coeff(0), ri(1));
assert_eq!(b.coeff(1), r(1, 2));
assert_eq!(b.coeff(2), r(-1, 8));
assert_eq!(b.coeff(3), r(1, 16));
assert_eq!(b.coeff(4), r(-5, 128));
}
#[test]
fn mul_consistency_with_series_truncation() {
let s = Fps::sin_series();
let c = Fps::cos_series();
let prod = s.mul(&c);
for k in 0..6u64 {
let n = 2 * k + 1;
let sign = if k % 2 == 0 { 1 } else { -1 };
let two_pow = Integer::from(2).pow((2 * k) as u32);
let expected = Rational::from((Integer::from(sign) * two_pow, factorial(n)));
assert_eq!(prod.coeff(n as usize), expected);
}
for k in 0..6 {
assert_eq!(prod.coeff(2 * k), ri(0));
}
}
#[test]
fn derivative_and_integral_inverse() {
let e = Fps::exp_series();
let de = e.derivative();
for n in 0..12 {
assert_eq!(de.coeff(n), e.coeff(n));
}
let ie = e.integral();
assert_eq!(ie.coeff(0), ri(0));
for n in 1..12 {
assert_eq!(ie.coeff(n), e.coeff(n));
}
}
#[test]
fn multiplicative_inverse() {
let one_minus_x = Fps::from_poly(&[ri(1), ri(-1)]);
let inv = one_minus_x.inverse().unwrap();
for n in 0..20 {
assert_eq!(inv.coeff(n), ri(1));
}
let e = Fps::exp_series();
let prod = e.mul(&e.inverse().unwrap());
assert_eq!(prod.coeff(0), ri(1));
for n in 1..15 {
assert_eq!(prod.coeff(n), ri(0));
}
}
#[test]
fn exp_log_roundtrip_to_order_30() {
let l = Fps::log1p_series(); let e = l.exp().unwrap();
assert_eq!(e.coeff(0), ri(1));
assert_eq!(e.coeff(1), ri(1));
for n in 2..=30 {
assert_eq!(e.coeff(n), ri(0), "coeff {n} should vanish");
}
}
#[test]
fn log_exp_roundtrip() {
let e = Fps::exp_series(); let l = e.log().unwrap();
assert_eq!(l.coeff(0), ri(0));
assert_eq!(l.coeff(1), ri(1));
for n in 2..=20 {
assert_eq!(l.coeff(n), ri(0));
}
}
#[test]
fn reversion_of_sin_is_arcsin() {
let s = Fps::sin_series();
let asin = s.revert().unwrap();
assert_eq!(asin.coeff(1), ri(1));
assert_eq!(asin.coeff(3), r(1, 6));
assert_eq!(asin.coeff(5), r(3, 40));
assert_eq!(asin.coeff(7), r(15, 336));
assert_eq!(asin.coeff(2), ri(0));
assert_eq!(asin.coeff(4), ri(0));
}
#[test]
fn reversion_roundtrip() {
let s = Fps::sin_series();
let rr = s.revert().unwrap().revert().unwrap();
for n in 0..10 {
assert_eq!(rr.coeff(n), s.coeff(n));
}
}
#[test]
fn composition_consistency() {
let e = Fps::exp_series();
let s = Fps::sin_series();
let comp = e.compose(&s).unwrap();
assert_eq!(comp.coeff(0), ri(1));
assert_eq!(comp.coeff(1), ri(1));
assert_eq!(comp.coeff(2), r(1, 2));
assert_eq!(comp.coeff(3), ri(0));
assert_eq!(comp.coeff(4), r(-1, 8));
assert_eq!(comp.coeff(5), r(-1, 15));
}
#[test]
fn catalan_numbers_via_binomial_half() {
let sqrt_1m4x = Fps::from_fn(|n| {
let mut binom = ri(1);
for j in 0..n {
binom *= r(1, 2) - ri(j as i64);
}
binom /= Rational::from(factorial(n as u64));
binom * Rational::from(Integer::from(-4).pow(n as u32))
});
let one = Fps::constant(ri(1));
let num = one.sub(&sqrt_1m4x); let catalan = Fps::from_fn(move |n| num.coeff(n + 1) / ri(2));
let expected = [1i64, 1, 2, 5, 14, 42, 132, 429];
for (n, &e) in expected.iter().enumerate() {
assert_eq!(catalan.coeff(n), ri(e), "Catalan C_{n}");
}
}
#[test]
fn catalan_via_implicit_equation() {
let catalan = Fps::implicit(|n, prev| {
if n == 0 {
ri(1)
} else {
let mut acc = ri(0);
for k in 0..n {
acc += prev[k].clone() * prev[n - 1 - k].clone();
}
acc
}
});
let expected = [1i64, 1, 2, 5, 14, 42, 132, 429, 1430];
for (n, &e) in expected.iter().enumerate() {
assert_eq!(catalan.coeff(n), ri(e), "Catalan C_{n}");
}
let sqrt_1m4x = Fps::from_fn(|n| {
let mut binom = ri(1);
for j in 0..n {
binom *= r(1, 2) - ri(j as i64);
}
binom /= Rational::from(factorial(n as u64));
binom * Rational::from(Integer::from(-4).pow(n as u32))
});
let one = Fps::constant(ri(1));
let num = one.sub(&sqrt_1m4x);
let closed = Fps::from_fn(move |n| num.coeff(n + 1) / ri(2));
for n in 0..12 {
assert_eq!(catalan.coeff(n), closed.coeff(n));
}
}
#[test]
fn nth_root_squares_back() {
let one_plus_x = Fps::from_poly(&[ri(1), ri(1)]);
let root = one_plus_x.nth_root(2).unwrap();
let sq = root.mul(&root);
assert_eq!(sq.coeff(0), ri(1));
assert_eq!(sq.coeff(1), ri(1));
for n in 2..15 {
assert_eq!(sq.coeff(n), ri(0));
}
}
#[test]
fn pow_binomial_matches_binomial_series() {
let x = Fps::x();
let alpha = r(2, 3);
let p = x.pow_binomial(alpha.clone()).unwrap();
let b = Fps::binomial_series(alpha);
for n in 0..12 {
assert_eq!(p.coeff(n), b.coeff(n));
}
}
#[test]
fn laziness_high_then_low() {
let e = Fps::exp_series();
let c40 = e.coeff(40);
assert_eq!(c40, Rational::from((Integer::from(1), factorial(40))));
let c10 = e.coeff(10);
assert_eq!(c10, Rational::from((Integer::from(1), factorial(10))));
assert!(e.cache.borrow().len() >= 41);
}
#[test]
fn from_rational_geometric() {
let f = Fps::from_rational(&[ri(1)], &[ri(1), ri(-1), ri(-1)]).unwrap();
let fib = [1i64, 1, 2, 3, 5, 8, 13, 21, 34, 55];
for (n, &v) in fib.iter().enumerate() {
assert_eq!(f.coeff(n), ri(v));
}
}
#[test]
fn from_rational_rejects_zero_denominator() {
let e = Fps::from_rational(&[ri(1)], &[ri(0), ri(1)]);
assert_eq!(e.unwrap_err(), FpsError::DenominatorVanishesAtZero);
}
#[test]
fn from_expr_matches_series() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let ex = pool.func("exp", vec![x]);
let fps = Fps::from_expr(ex, x, &pool).unwrap();
let known = Fps::exp_series();
for n in 0..12 {
assert_eq!(fps.coeff(n), known.coeff(n), "coeff {n}");
}
let c20 = fps.coeff(20);
assert_eq!(c20, known.coeff(20));
assert_eq!(fps.coeff(5), known.coeff(5));
}
#[test]
fn from_expr_rejects_polar() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let inv_x = pool.pow(x, pool.integer(-1));
let e = Fps::from_expr(inv_x, x, &pool);
assert_eq!(e.unwrap_err(), FpsError::NotAnalyticAtZero);
}
#[test]
fn to_expr_format() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let e = Fps::exp_series();
let expr = e.to_expr(x, 4, &pool);
fn has_big_o(id: ExprId, pool: &ExprPool) -> bool {
match pool.get(id) {
ExprData::BigO(_) => true,
ExprData::Add(xs) | ExprData::Mul(xs) => xs.iter().any(|e| has_big_o(*e, pool)),
ExprData::Pow { base, exp } => has_big_o(base, pool) || has_big_o(exp, pool),
_ => false,
}
}
assert!(has_big_o(expr, &pool));
}
#[test]
fn inverse_requires_nonzero_constant() {
let f = Fps::x(); assert_eq!(
f.inverse().unwrap_err(),
FpsError::ConstantTermMustBeNonzero
);
}
#[test]
fn exp_requires_zero_constant() {
let f = Fps::constant(ri(1));
assert_eq!(f.exp().unwrap_err(), FpsError::ConstantTermMustBeZero);
}
#[test]
fn log_requires_unit_constant() {
let f = Fps::x();
assert_eq!(f.log().unwrap_err(), FpsError::ConstantTermMustBeOne);
}
}