use crate::ast::{BinaryOp, Expression, Function, Variable};
use std::collections::HashMap;
use std::fmt;
fn try_expr_to_f64(expr: &Expression) -> Option<f64> {
use crate::ast::{SymbolicConstant, UnaryOp};
match expr {
Expression::Integer(n) => Some(*n as f64),
Expression::Float(f) => Some(*f),
Expression::Rational(r) => Some(*r.numer() as f64 / *r.denom() as f64),
Expression::Constant(c) => match c {
SymbolicConstant::Pi => Some(std::f64::consts::PI),
SymbolicConstant::E => Some(std::f64::consts::E),
SymbolicConstant::I => None,
},
Expression::Unary(op, inner) => {
let val = try_expr_to_f64(inner)?;
match op {
UnaryOp::Neg => Some(-val),
UnaryOp::Abs => Some(val.abs()),
_ => None,
}
}
Expression::Binary(op, left, right) => {
let l = try_expr_to_f64(left)?;
let r = try_expr_to_f64(right)?;
match op {
BinaryOp::Add => Some(l + r),
BinaryOp::Sub => Some(l - r),
BinaryOp::Mul => Some(l * r),
BinaryOp::Div if r.abs() > 1e-15 => Some(l / r),
_ => None,
}
}
Expression::Power(base, exp) => {
let b = try_expr_to_f64(base)?;
let e = try_expr_to_f64(exp)?;
Some(b.powf(e))
}
_ => None,
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum SeriesError {
CannotExpand(String),
InvalidCenter(String),
DivisionByZero,
DerivativeFailed(String),
EvaluationFailed(String),
InvalidOrder(String),
}
impl fmt::Display for SeriesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SeriesError::CannotExpand(msg) => write!(f, "Cannot expand expression: {}", msg),
SeriesError::InvalidCenter(msg) => write!(f, "Invalid center point: {}", msg),
SeriesError::DivisionByZero => write!(f, "Division by zero in series expansion"),
SeriesError::DerivativeFailed(msg) => write!(f, "Differentiation failed: {}", msg),
SeriesError::EvaluationFailed(msg) => write!(f, "Evaluation at center failed: {}", msg),
SeriesError::InvalidOrder(msg) => write!(f, "Invalid order: {}", msg),
}
}
}
impl std::error::Error for SeriesError {}
pub type SeriesResult<T> = Result<T, SeriesError>;
#[derive(Debug, Clone, PartialEq)]
pub struct SeriesTerm {
pub coefficient: Expression,
pub power: u32,
}
impl SeriesTerm {
pub fn new(coefficient: Expression, power: u32) -> Self {
SeriesTerm { coefficient, power }
}
pub fn is_zero(&self) -> bool {
matches!(&self.coefficient, Expression::Integer(0))
|| matches!(&self.coefficient, Expression::Float(x) if x.abs() < 1e-15)
}
}
impl fmt::Display for SeriesTerm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.power == 0 {
write!(f, "{}", self.coefficient)
} else if self.power == 1 {
write!(f, "{}·x", self.coefficient)
} else {
write!(f, "{}·x^{}", self.coefficient, self.power)
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RemainderTerm {
Lagrange {
bound: Expression,
order: u32,
},
BigO {
order: u32,
},
}
impl RemainderTerm {
pub fn order(&self) -> u32 {
match self {
RemainderTerm::Lagrange { order, .. } => *order,
RemainderTerm::BigO { order } => *order,
}
}
pub fn to_latex(&self) -> String {
match self {
RemainderTerm::Lagrange { bound, order } => {
format!("R_{{{}}}(x) \\leq {}", order, bound)
}
RemainderTerm::BigO { order } => {
format!("O(x^{{{}}})", order)
}
}
}
}
impl fmt::Display for RemainderTerm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RemainderTerm::Lagrange { order, .. } => write!(f, "R_{}(x)", order),
RemainderTerm::BigO { order } => write!(f, "O(x^{})", order),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Series {
pub terms: Vec<SeriesTerm>,
pub center: Expression,
pub variable: Variable,
pub order: u32,
pub remainder: Option<RemainderTerm>,
}
impl Series {
pub fn new(variable: Variable, center: Expression, order: u32) -> Self {
Series {
terms: Vec::new(),
center,
variable,
order,
remainder: None,
}
}
pub fn add_term(&mut self, term: SeriesTerm) {
if !term.is_zero() {
self.terms.push(term);
}
}
pub fn set_remainder(&mut self, remainder: RemainderTerm) {
self.remainder = Some(remainder);
}
pub fn term_count(&self) -> usize {
self.terms.len()
}
pub fn get_term(&self, power: u32) -> Option<&SeriesTerm> {
self.terms.iter().find(|t| t.power == power)
}
pub fn to_expression(&self) -> Expression {
if self.terms.is_empty() {
return Expression::Integer(0);
}
let var_expr = Expression::Variable(self.variable.clone());
let is_centered_at_zero = matches!(&self.center, Expression::Integer(0))
|| matches!(&self.center, Expression::Float(x) if x.abs() < 1e-15);
let mut result: Option<Expression> = None;
for term in &self.terms {
let power_base = if is_centered_at_zero {
var_expr.clone()
} else {
Expression::Binary(
BinaryOp::Sub,
Box::new(var_expr.clone()),
Box::new(self.center.clone()),
)
};
let term_expr = if term.power == 0 {
term.coefficient.clone()
} else if term.power == 1 {
Expression::Binary(
BinaryOp::Mul,
Box::new(term.coefficient.clone()),
Box::new(power_base),
)
} else {
Expression::Binary(
BinaryOp::Mul,
Box::new(term.coefficient.clone()),
Box::new(Expression::Power(
Box::new(power_base),
Box::new(Expression::Integer(term.power as i64)),
)),
)
};
result = Some(match result {
None => term_expr,
Some(acc) => Expression::Binary(BinaryOp::Add, Box::new(acc), Box::new(term_expr)),
});
}
result.unwrap_or(Expression::Integer(0)).simplify()
}
pub fn to_latex(&self) -> String {
if self.terms.is_empty() {
return "0".to_string();
}
let is_centered_at_zero = matches!(&self.center, Expression::Integer(0));
let var_name = &self.variable.name;
let mut parts = Vec::new();
for (i, term) in self.terms.iter().enumerate() {
let coeff_str = format_coefficient_latex(&term.coefficient);
let term_str = if term.power == 0 {
coeff_str
} else {
let var_part = if is_centered_at_zero {
if term.power == 1 {
var_name.clone()
} else {
format!("{}^{{{}}}", var_name, term.power)
}
} else {
if term.power == 1 {
format!("({} - {})", var_name, self.center)
} else {
format!("({} - {})^{{{}}}", var_name, self.center, term.power)
}
};
if coeff_str == "1" {
var_part
} else if coeff_str == "-1" {
format!("-{}", var_part)
} else {
format!("{} {}", coeff_str, var_part)
}
};
if i == 0 {
parts.push(term_str);
} else {
if term_str.starts_with('-') {
parts.push(format!(" - {}", &term_str[1..]));
} else {
parts.push(format!(" + {}", term_str));
}
}
}
let mut result = parts.join("");
if let Some(ref remainder) = self.remainder {
result.push_str(&format!(" + {}", remainder.to_latex()));
}
result
}
pub fn evaluate(&self, x: f64) -> Option<f64> {
let center_val = self.center.evaluate(&HashMap::new())?;
let dx = x - center_val;
let mut sum = 0.0;
for term in &self.terms {
let coeff = term.coefficient.evaluate(&HashMap::new())?;
sum += coeff * dx.powi(term.power as i32);
}
Some(sum)
}
fn coeff_f64(&self, power: u32) -> f64 {
self.get_term(power)
.and_then(|t| try_expr_to_f64(&t.coefficient))
.unwrap_or(0.0)
}
pub fn reciprocal(&self) -> SeriesResult<Series> {
let a0 = self.coeff_f64(0);
if a0.abs() < 1e-15 {
return Err(SeriesError::CannotExpand(
"Cannot compute reciprocal: constant term is zero".into(),
));
}
let mut result = Series::new(self.variable.clone(), self.center.clone(), self.order);
result.add_term(SeriesTerm::new(Expression::Float(1.0 / a0), 0));
for n in 1..=self.order {
let mut sum = 0.0;
for k in 1..=n {
let a_k = self.coeff_f64(k);
let b_n_k = result.coeff_f64(n - k);
sum += a_k * b_n_k;
}
let b_n = -sum / a0;
if b_n.abs() > 1e-15 {
result.add_term(SeriesTerm::new(Expression::Float(b_n), n));
}
}
Ok(result)
}
pub fn differentiate(&self) -> Series {
let new_order = if self.order > 0 { self.order - 1 } else { 0 };
let mut result = Series::new(self.variable.clone(), self.center.clone(), new_order);
for term in &self.terms {
if term.power > 0 {
let new_coeff = Expression::Binary(
BinaryOp::Mul,
Box::new(Expression::Integer(term.power as i64)),
Box::new(term.coefficient.clone()),
)
.simplify();
result.add_term(SeriesTerm::new(new_coeff, term.power - 1));
}
}
result
}
pub fn integrate(&self, constant: Expression) -> Series {
let mut result = Series::new(self.variable.clone(), self.center.clone(), self.order + 1);
result.add_term(SeriesTerm::new(constant, 0));
for term in &self.terms {
let new_coeff = Expression::Binary(
BinaryOp::Div,
Box::new(term.coefficient.clone()),
Box::new(Expression::Integer((term.power + 1) as i64)),
)
.simplify();
result.add_term(SeriesTerm::new(new_coeff, term.power + 1));
}
result
}
}
use std::ops::{Add, Div, Mul, Sub};
impl Add for Series {
type Output = SeriesResult<Series>;
fn add(self, rhs: Series) -> SeriesResult<Series> {
if self.center != rhs.center {
return Err(SeriesError::InvalidCenter(
"Cannot add series with different centers".into(),
));
}
if self.variable.name != rhs.variable.name {
return Err(SeriesError::CannotExpand(
"Cannot add series with different variables".into(),
));
}
let min_order = self.order.min(rhs.order);
let mut result = Series::new(self.variable.clone(), self.center.clone(), min_order);
let mut coeffs: HashMap<u32, Expression> = HashMap::new();
for term in &self.terms {
if term.power <= min_order {
coeffs.insert(term.power, term.coefficient.clone());
}
}
for term in &rhs.terms {
if term.power <= min_order {
let coeff = coeffs.entry(term.power).or_insert(Expression::Integer(0));
*coeff = Expression::Binary(
BinaryOp::Add,
Box::new(coeff.clone()),
Box::new(term.coefficient.clone()),
)
.simplify();
}
}
for (power, coeff) in coeffs {
result.add_term(SeriesTerm::new(coeff, power));
}
result.terms.sort_by_key(|t| t.power);
Ok(result)
}
}
impl Sub for Series {
type Output = SeriesResult<Series>;
fn sub(self, rhs: Series) -> SeriesResult<Series> {
if self.center != rhs.center {
return Err(SeriesError::InvalidCenter(
"Cannot subtract series with different centers".into(),
));
}
if self.variable.name != rhs.variable.name {
return Err(SeriesError::CannotExpand(
"Cannot subtract series with different variables".into(),
));
}
let min_order = self.order.min(rhs.order);
let mut result = Series::new(self.variable.clone(), self.center.clone(), min_order);
let mut coeffs: HashMap<u32, Expression> = HashMap::new();
for term in &self.terms {
if term.power <= min_order {
coeffs.insert(term.power, term.coefficient.clone());
}
}
for term in &rhs.terms {
if term.power <= min_order {
let coeff = coeffs.entry(term.power).or_insert(Expression::Integer(0));
*coeff = Expression::Binary(
BinaryOp::Sub,
Box::new(coeff.clone()),
Box::new(term.coefficient.clone()),
)
.simplify();
}
}
for (power, coeff) in coeffs {
result.add_term(SeriesTerm::new(coeff, power));
}
result.terms.sort_by_key(|t| t.power);
Ok(result)
}
}
impl Mul for Series {
type Output = SeriesResult<Series>;
fn mul(self, rhs: Series) -> SeriesResult<Series> {
if self.center != rhs.center {
return Err(SeriesError::InvalidCenter(
"Cannot multiply series with different centers".into(),
));
}
if self.variable.name != rhs.variable.name {
return Err(SeriesError::CannotExpand(
"Cannot multiply series with different variables".into(),
));
}
let min_order = self.order.min(rhs.order);
let mut result = Series::new(self.variable.clone(), self.center.clone(), min_order);
let mut coeffs: HashMap<u32, Expression> = HashMap::new();
for term_a in &self.terms {
for term_b in &rhs.terms {
let new_power = term_a.power + term_b.power;
if new_power <= min_order {
let product = Expression::Binary(
BinaryOp::Mul,
Box::new(term_a.coefficient.clone()),
Box::new(term_b.coefficient.clone()),
)
.simplify();
let coeff = coeffs.entry(new_power).or_insert(Expression::Integer(0));
*coeff = Expression::Binary(
BinaryOp::Add,
Box::new(coeff.clone()),
Box::new(product),
)
.simplify();
}
}
}
for (power, coeff) in coeffs {
result.add_term(SeriesTerm::new(coeff, power));
}
result.terms.sort_by_key(|t| t.power);
Ok(result)
}
}
impl Div for Series {
type Output = SeriesResult<Series>;
fn div(self, rhs: Series) -> SeriesResult<Series> {
let reciprocal = rhs.reciprocal()?;
self * reciprocal
}
}
pub fn compose_series(outer: &Series, inner: &Series) -> SeriesResult<Series> {
if outer.center != inner.center {
return Err(SeriesError::InvalidCenter(
"Cannot compose series with different centers".into(),
));
}
if outer.variable.name != inner.variable.name {
return Err(SeriesError::CannotExpand(
"Cannot compose series with different variables".into(),
));
}
let inner_a0 = inner.coeff_f64(0);
if inner_a0.abs() > 1e-15 {
return Err(SeriesError::CannotExpand(
"Cannot compose series: inner series must have zero constant term".into(),
));
}
let order = outer.order.min(inner.order);
let mut result = Series::new(outer.variable.clone(), outer.center.clone(), order);
let mut inner_powers: Vec<Series> = Vec::new();
let mut inner_pow_0 = Series::new(inner.variable.clone(), inner.center.clone(), order);
inner_pow_0.add_term(SeriesTerm::new(Expression::Integer(1), 0));
inner_powers.push(inner_pow_0);
for _ in 1..=order {
let prev = inner_powers.last().unwrap().clone();
let next = (prev * inner.clone())?;
inner_powers.push(next);
}
let mut coeffs: HashMap<u32, Expression> = HashMap::new();
for term in &outer.terms {
if term.power as usize <= inner_powers.len() - 1 {
let inner_pow = &inner_powers[term.power as usize];
for inner_term in &inner_pow.terms {
if inner_term.power <= order {
let contribution = Expression::Binary(
BinaryOp::Mul,
Box::new(term.coefficient.clone()),
Box::new(inner_term.coefficient.clone()),
)
.simplify();
let coeff = coeffs
.entry(inner_term.power)
.or_insert(Expression::Integer(0));
*coeff = Expression::Binary(
BinaryOp::Add,
Box::new(coeff.clone()),
Box::new(contribution),
)
.simplify();
}
}
}
}
for (power, coeff) in coeffs {
result.add_term(SeriesTerm::new(coeff, power));
}
result.terms.sort_by_key(|t| t.power);
Ok(result)
}
pub fn reversion(series: &Series) -> SeriesResult<Series> {
let a0 = series.coeff_f64(0);
let a1 = series.coeff_f64(1);
if a0.abs() > 1e-15 {
return Err(SeriesError::CannotExpand(
"Cannot compute reversion: constant term must be zero".into(),
));
}
if a1.abs() < 1e-15 {
return Err(SeriesError::CannotExpand(
"Cannot compute reversion: linear coefficient must be non-zero".into(),
));
}
let order = series.order;
let mut result = Series::new(series.variable.clone(), series.center.clone(), order);
let b1 = 1.0 / a1;
result.add_term(SeriesTerm::new(Expression::Float(b1), 1));
for n in 2..=order {
let mut sum = 0.0;
for k in 1..=n {
let tk_coeff_n = compute_power_coeff(&result, k, n);
let a_k = series.coeff_f64(k);
sum += a_k * tk_coeff_n;
}
let contribution_without_b_n = sum - a1 * 0.0; let b_n = -contribution_without_b_n / a1;
if b_n.abs() > 1e-15 {
result.add_term(SeriesTerm::new(Expression::Float(b_n), n));
}
}
result.terms.sort_by_key(|t| t.power);
Ok(result)
}
fn compute_power_coeff(series: &Series, k: u32, n: u32) -> f64 {
if k == 0 {
return if n == 0 { 1.0 } else { 0.0 };
}
if k == 1 {
return series.coeff_f64(n);
}
let mut sum = 0.0;
for j in 0..=n {
let t_j = series.coeff_f64(j);
let t_k1_nj = compute_power_coeff(series, k - 1, n - j);
sum += t_j * t_k1_nj;
}
sum
}
fn format_coefficient_latex(expr: &Expression) -> String {
match expr {
Expression::Integer(n) => n.to_string(),
Expression::Float(x) => {
if (x - x.round()).abs() < 1e-10 {
format!("{}", x.round() as i64)
} else {
format!("{:.6}", x)
}
}
Expression::Rational(r) => {
format!("\\frac{{{}}}{{{}}}", r.numer(), r.denom())
}
_ => format!("{}", expr),
}
}
pub fn factorial(n: u32) -> u64 {
if n <= 1 {
1
} else {
(2..=n as u64).product()
}
}
pub fn factorial_expr(n: u32) -> Expression {
Expression::Integer(factorial(n) as i64)
}
pub fn evaluate_at(
expr: &Expression,
var: &Variable,
value: &Expression,
) -> SeriesResult<Expression> {
let substituted = substitute(expr, var, value);
let simplified = substituted.simplify();
if let Some(val) = simplified.evaluate(&HashMap::new()) {
if val.is_nan() {
return Err(SeriesError::EvaluationFailed(format!(
"Expression evaluates to NaN at {} = {}",
var.name, value
)));
}
if val.is_infinite() {
return Err(SeriesError::EvaluationFailed(format!(
"Expression evaluates to infinity at {} = {}",
var.name, value
)));
}
return Ok(Expression::Float(val));
}
Ok(simplified)
}
fn substitute(expr: &Expression, var: &Variable, value: &Expression) -> Expression {
match expr {
Expression::Variable(v) if v.name == var.name => value.clone(),
Expression::Binary(op, left, right) => Expression::Binary(
*op,
Box::new(substitute(left, var, value)),
Box::new(substitute(right, var, value)),
),
Expression::Unary(op, inner) => {
Expression::Unary(*op, Box::new(substitute(inner, var, value)))
}
Expression::Function(func, args) => Expression::Function(
func.clone(),
args.iter().map(|a| substitute(a, var, value)).collect(),
),
Expression::Power(base, exp) => Expression::Power(
Box::new(substitute(base, var, value)),
Box::new(substitute(exp, var, value)),
),
_ => expr.clone(),
}
}
pub fn compute_nth_derivative(
expr: &Expression,
var: &Variable,
n: u32,
) -> SeriesResult<Expression> {
let mut result = expr.clone();
for _ in 0..n {
result = result.differentiate(&var.name);
result = result.simplify();
}
Ok(result)
}
pub fn taylor(
expr: &Expression,
var: &Variable,
center: &Expression,
order: u32,
) -> SeriesResult<Series> {
if let Some(series) = try_known_series(expr, var, center, order) {
return Ok(series);
}
let mut series = Series::new(var.clone(), center.clone(), order);
for n in 0..=order {
let nth_deriv = compute_nth_derivative(expr, var, n)?;
let deriv_at_center = evaluate_at(&nth_deriv, var, center)?;
let n_fact = factorial(n) as i64;
let coefficient = if n_fact == 1 {
deriv_at_center
} else {
Expression::Binary(
BinaryOp::Div,
Box::new(deriv_at_center),
Box::new(Expression::Integer(n_fact)),
)
.simplify()
};
let term = SeriesTerm::new(coefficient, n);
series.add_term(term);
}
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
Ok(series)
}
pub fn maclaurin(expr: &Expression, var: &Variable, order: u32) -> SeriesResult<Series> {
taylor(expr, var, &Expression::Integer(0), order)
}
fn try_known_series(
expr: &Expression,
var: &Variable,
center: &Expression,
order: u32,
) -> Option<Series> {
if !matches!(center, Expression::Integer(0)) {
return None;
}
match expr {
Expression::Function(Function::Exp, args) if args.len() == 1 => {
if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
return Some(exp_series(var, order));
}
}
Expression::Function(Function::Sin, args) if args.len() == 1 => {
if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
return Some(sin_series(var, order));
}
}
Expression::Function(Function::Cos, args) if args.len() == 1 => {
if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
return Some(cos_series(var, order));
}
}
Expression::Function(Function::Ln, args) if args.len() == 1 => {
if let Expression::Binary(BinaryOp::Add, left, right) = &args[0] {
if matches!(**left, Expression::Integer(1))
&& matches!(**right, Expression::Variable(ref v) if v.name == var.name)
{
return Some(ln_1_plus_x_series(var, order));
}
}
}
Expression::Function(Function::Atan, args) if args.len() == 1 => {
if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
return Some(arctan_series(var, order));
}
}
_ => {}
}
None
}
pub fn exp_series(var: &Variable, order: u32) -> Series {
let mut series = Series::new(var.clone(), Expression::Integer(0), order);
for n in 0..=order {
let coeff = if n == 0 {
Expression::Integer(1)
} else {
let n_fact = factorial(n);
Expression::Rational(num_rational::Ratio::new(1, n_fact as i64))
};
series.add_term(SeriesTerm::new(coeff, n));
}
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
series
}
pub fn sin_series(var: &Variable, order: u32) -> Series {
let mut series = Series::new(var.clone(), Expression::Integer(0), order);
let mut n = 0u32;
while 2 * n + 1 <= order {
let power = 2 * n + 1;
let sign = if n % 2 == 0 { 1i64 } else { -1i64 };
let fact = factorial(power) as i64;
let coeff = Expression::Rational(num_rational::Ratio::new(sign, fact));
series.add_term(SeriesTerm::new(coeff, power));
n += 1;
}
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
series
}
pub fn cos_series(var: &Variable, order: u32) -> Series {
let mut series = Series::new(var.clone(), Expression::Integer(0), order);
let mut n = 0u32;
while 2 * n <= order {
let power = 2 * n;
let sign = if n % 2 == 0 { 1i64 } else { -1i64 };
let fact = if power == 0 {
1
} else {
factorial(power) as i64
};
let coeff = Expression::Rational(num_rational::Ratio::new(sign, fact));
series.add_term(SeriesTerm::new(coeff, power));
n += 1;
}
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
series
}
pub fn ln_1_plus_x_series(var: &Variable, order: u32) -> Series {
let mut series = Series::new(var.clone(), Expression::Integer(0), order);
for n in 1..=order {
let sign = if n % 2 == 1 { 1i64 } else { -1i64 };
let coeff = Expression::Rational(num_rational::Ratio::new(sign, n as i64));
series.add_term(SeriesTerm::new(coeff, n));
}
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
series
}
pub fn arctan_series(var: &Variable, order: u32) -> Series {
let mut series = Series::new(var.clone(), Expression::Integer(0), order);
let mut n = 0u32;
while 2 * n + 1 <= order {
let power = 2 * n + 1;
let sign = if n % 2 == 0 { 1i64 } else { -1i64 };
let coeff = Expression::Rational(num_rational::Ratio::new(sign, power as i64));
series.add_term(SeriesTerm::new(coeff, power));
n += 1;
}
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
series
}
pub fn binomial_series(exponent: &Expression, var: &Variable, order: u32) -> SeriesResult<Series> {
let mut series = Series::new(var.clone(), Expression::Integer(0), order);
let a = match exponent.evaluate(&HashMap::new()) {
Some(val) => val,
None => {
return Err(SeriesError::CannotExpand(
"Binomial series requires numeric exponent".to_string(),
))
}
};
let mut binom_coeff = 1.0;
for n in 0..=order {
if n > 0 {
binom_coeff *= (a - (n as f64 - 1.0)) / (n as f64);
}
if binom_coeff.abs() > 1e-15 {
let coeff = if (binom_coeff - binom_coeff.round()).abs() < 1e-10 {
Expression::Integer(binom_coeff.round() as i64)
} else {
Expression::Float(binom_coeff)
};
series.add_term(SeriesTerm::new(coeff, n));
}
if a.fract() == 0.0 && a >= 0.0 && n as f64 >= a {
break;
}
}
if a.fract() != 0.0 || a < 0.0 {
series.set_remainder(RemainderTerm::BigO { order: order + 1 });
}
Ok(series)
}
#[derive(Debug, Clone, PartialEq)]
pub enum SingularityType {
Removable,
Pole(u32),
Essential,
}
impl fmt::Display for SingularityType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SingularityType::Removable => write!(f, "removable singularity"),
SingularityType::Pole(order) => write!(f, "pole of order {}", order),
SingularityType::Essential => write!(f, "essential singularity"),
}
}
}
#[derive(Debug, Clone)]
pub struct Singularity {
pub location: Expression,
pub singularity_type: SingularityType,
}
impl Singularity {
pub fn new(location: Expression, singularity_type: SingularityType) -> Self {
Self {
location,
singularity_type,
}
}
pub fn is_pole(&self) -> bool {
matches!(self.singularity_type, SingularityType::Pole(_))
}
pub fn pole_order(&self) -> Option<u32> {
match self.singularity_type {
SingularityType::Pole(order) => Some(order),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct LaurentSeries {
pub positive_terms: Vec<SeriesTerm>,
pub negative_terms: Vec<SeriesTerm>,
pub center: Expression,
pub variable: Variable,
pub principal_part_order: u32,
pub analytic_part_order: u32,
}
impl LaurentSeries {
pub fn new(variable: Variable, center: Expression, neg_order: u32, pos_order: u32) -> Self {
LaurentSeries {
positive_terms: Vec::new(),
negative_terms: Vec::new(),
center,
variable,
principal_part_order: neg_order,
analytic_part_order: pos_order,
}
}
pub fn add_positive_term(&mut self, term: SeriesTerm) {
if !term.is_zero() {
self.positive_terms.push(term);
}
}
pub fn add_negative_term(&mut self, coefficient: Expression, neg_power: u32) {
if neg_power > 0 {
let term = SeriesTerm::new(coefficient, neg_power);
if !term.is_zero() {
self.negative_terms.push(term);
}
}
}
pub fn residue(&self) -> Expression {
self.negative_terms
.iter()
.find(|t| t.power == 1)
.map(|t| t.coefficient.clone())
.unwrap_or(Expression::Integer(0))
}
pub fn principal_part(&self) -> LaurentSeries {
let mut result = LaurentSeries::new(
self.variable.clone(),
self.center.clone(),
self.principal_part_order,
0,
);
result.negative_terms = self.negative_terms.clone();
result
}
pub fn analytic_part(&self) -> Series {
let mut result = Series::new(
self.variable.clone(),
self.center.clone(),
self.analytic_part_order,
);
for term in &self.positive_terms {
result.add_term(term.clone());
}
result
}
pub fn is_taylor(&self) -> bool {
self.negative_terms.is_empty()
}
pub fn to_taylor(&self) -> Option<Series> {
if self.is_taylor() {
Some(self.analytic_part())
} else {
None
}
}
pub fn evaluate(&self, x: f64) -> Option<f64> {
let center_val = try_expr_to_f64(&self.center)?;
let dx = x - center_val;
if dx.abs() < 1e-15 && !self.negative_terms.is_empty() {
return None;
}
let mut sum = 0.0;
for term in &self.positive_terms {
let coeff = try_expr_to_f64(&term.coefficient)?;
sum += coeff * dx.powi(term.power as i32);
}
for term in &self.negative_terms {
let coeff = try_expr_to_f64(&term.coefficient)?;
sum += coeff / dx.powi(term.power as i32);
}
Some(sum)
}
pub fn to_latex(&self) -> String {
let is_centered_at_zero = matches!(&self.center, Expression::Integer(0))
|| matches!(&self.center, Expression::Float(x) if x.abs() < 1e-15);
let var_name = &self.variable.name;
let mut parts = Vec::new();
let mut neg_sorted: Vec<_> = self.negative_terms.iter().collect();
neg_sorted.sort_by(|a, b| b.power.cmp(&a.power));
for term in neg_sorted {
let coeff_str = format_coefficient_latex(&term.coefficient);
let var_part = if is_centered_at_zero {
format!("{}^{{-{}}}", var_name, term.power)
} else {
format!("({} - {})^{{-{}}}", var_name, self.center, term.power)
};
let term_str = if coeff_str == "1" {
var_part
} else if coeff_str == "-1" {
format!("-{}", var_part)
} else {
format!("{} {}", coeff_str, var_part)
};
if parts.is_empty() {
parts.push(term_str);
} else if term_str.starts_with('-') {
parts.push(format!(" - {}", &term_str[1..]));
} else {
parts.push(format!(" + {}", term_str));
}
}
for term in &self.positive_terms {
let coeff_str = format_coefficient_latex(&term.coefficient);
let term_str = if term.power == 0 {
coeff_str
} else {
let var_part = if is_centered_at_zero {
if term.power == 1 {
var_name.clone()
} else {
format!("{}^{{{}}}", var_name, term.power)
}
} else if term.power == 1 {
format!("({} - {})", var_name, self.center)
} else {
format!("({} - {})^{{{}}}", var_name, self.center, term.power)
};
if coeff_str == "1" {
var_part
} else if coeff_str == "-1" {
format!("-{}", var_part)
} else {
format!("{} {}", coeff_str, var_part)
}
};
if parts.is_empty() {
parts.push(term_str);
} else if term_str.starts_with('-') {
parts.push(format!(" - {}", &term_str[1..]));
} else {
parts.push(format!(" + {}", term_str));
}
}
if parts.is_empty() {
"0".to_string()
} else {
parts.join("")
}
}
}
impl fmt::Display for LaurentSeries {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let latex = self.to_latex();
let plain = latex
.replace("^{-", "^(-")
.replace("^{", "^")
.replace("}", ")")
.replace("{", "");
write!(f, "{}", plain)
}
}
pub fn laurent(
expr: &Expression,
var: &Variable,
center: &Expression,
neg_order: u32,
pos_order: u32,
) -> SeriesResult<LaurentSeries> {
let mut series = LaurentSeries::new(var.clone(), center.clone(), neg_order, pos_order);
match expr {
Expression::Binary(BinaryOp::Div, num, denom) => {
if is_power_of_var(denom, var, center) {
let power = get_var_power(denom, var);
if power > 0 {
let num_taylor = taylor(num, var, center, pos_order + power as u32)?;
for term in &num_taylor.terms {
let new_power = term.power as i32 - power;
if new_power < 0 && (-new_power as u32) <= neg_order {
series.add_negative_term(term.coefficient.clone(), (-new_power) as u32);
} else if new_power >= 0 && (new_power as u32) <= pos_order {
series.add_positive_term(SeriesTerm::new(
term.coefficient.clone(),
new_power as u32,
));
}
}
return Ok(series);
}
}
let num_taylor = taylor(num, var, center, pos_order + neg_order)?;
let denom_taylor = taylor(denom, var, center, pos_order + neg_order)?;
let denom_zero_order = find_leading_power(&denom_taylor);
if denom_zero_order > 0 {
let result = divide_series(&num_taylor, &denom_taylor, neg_order, pos_order)?;
for (power, coeff) in result {
if power < 0 && (-power as u32) <= neg_order {
series.add_negative_term(coeff, (-power) as u32);
} else if power >= 0 && (power as u32) <= pos_order {
series.add_positive_term(SeriesTerm::new(coeff, power as u32));
}
}
} else {
let regular_taylor = taylor(expr, var, center, pos_order)?;
for term in regular_taylor.terms {
series.add_positive_term(term);
}
}
}
Expression::Power(base, exp) => {
if let Expression::Unary(crate::ast::UnaryOp::Neg, inner_exp) = exp.as_ref() {
if is_var_minus_center(base, var, center) {
if let Some(n) = extract_positive_integer(inner_exp) {
if n <= neg_order {
series.add_negative_term(Expression::Integer(1), n);
}
return Ok(series);
}
}
}
let regular_taylor = taylor(expr, var, center, pos_order)?;
for term in regular_taylor.terms {
series.add_positive_term(term);
}
}
_ => {
let regular_taylor = taylor(expr, var, center, pos_order)?;
for term in regular_taylor.terms {
series.add_positive_term(term);
}
}
}
Ok(series)
}
pub fn residue(expr: &Expression, var: &Variable, pole: &Expression) -> SeriesResult<Expression> {
let laurent_series = laurent(expr, var, pole, 5, 0)?;
Ok(laurent_series.residue())
}
pub fn pole_order(expr: &Expression, var: &Variable, point: &Expression) -> SeriesResult<u32> {
let laurent_series = laurent(expr, var, point, 10, 0)?;
let mut max_order = 0;
for term in &laurent_series.negative_terms {
if term.power > max_order {
max_order = term.power;
}
}
Ok(max_order)
}
pub fn find_singularities(expr: &Expression, var: &Variable) -> Vec<Singularity> {
let mut singularities = Vec::new();
match expr {
Expression::Binary(BinaryOp::Div, _num, denom) => {
let zeros = find_zeros_of_expression(denom, var);
for zero in zeros {
let order = get_zero_multiplicity(denom, var, &zero);
let sing_type = if order > 0 {
SingularityType::Pole(order)
} else {
SingularityType::Removable
};
singularities.push(Singularity::new(zero, sing_type));
}
}
_ => {
}
}
singularities
}
fn is_power_of_var(expr: &Expression, var: &Variable, center: &Expression) -> bool {
match expr {
Expression::Variable(v) if v.name == var.name => {
matches!(center, Expression::Integer(0))
}
Expression::Power(base, _) => is_var_minus_center(base, var, center),
Expression::Binary(BinaryOp::Sub, left, right) => {
matches!(left.as_ref(), Expression::Variable(v) if v.name == var.name)
&& expressions_equal(right, center)
}
_ => false,
}
}
fn is_var_minus_center(expr: &Expression, var: &Variable, center: &Expression) -> bool {
match expr {
Expression::Variable(v) if v.name == var.name => {
matches!(center, Expression::Integer(0))
}
Expression::Binary(BinaryOp::Sub, left, right) => {
matches!(left.as_ref(), Expression::Variable(v) if v.name == var.name)
&& expressions_equal(right, center)
}
_ => false,
}
}
fn get_var_power(expr: &Expression, var: &Variable) -> i32 {
match expr {
Expression::Variable(v) if v.name == var.name => 1,
Expression::Power(base, exp) => {
if let Expression::Variable(v) = base.as_ref() {
if v.name == var.name {
if let Some(n) = extract_integer(exp) {
return n as i32;
}
}
}
1
}
Expression::Binary(BinaryOp::Sub, left, _) => {
if let Expression::Variable(v) = left.as_ref() {
if v.name == var.name {
return 1;
}
}
0
}
_ => 0,
}
}
fn extract_integer(expr: &Expression) -> Option<i64> {
match expr {
Expression::Integer(n) => Some(*n),
Expression::Float(f) if f.fract() == 0.0 => Some(*f as i64),
_ => None,
}
}
fn extract_positive_integer(expr: &Expression) -> Option<u32> {
extract_integer(expr).and_then(|n| if n > 0 { Some(n as u32) } else { None })
}
fn expressions_equal(a: &Expression, b: &Expression) -> bool {
match (a, b) {
(Expression::Integer(x), Expression::Integer(y)) => x == y,
(Expression::Float(x), Expression::Float(y)) => (x - y).abs() < 1e-15,
(Expression::Integer(x), Expression::Float(y))
| (Expression::Float(y), Expression::Integer(x)) => (*x as f64 - y).abs() < 1e-15,
(Expression::Variable(v1), Expression::Variable(v2)) => v1.name == v2.name,
_ => false,
}
}
fn find_leading_power(series: &Series) -> u32 {
series
.terms
.iter()
.filter(|t| !t.is_zero())
.map(|t| t.power)
.min()
.unwrap_or(0)
}
fn divide_series(
num: &Series,
denom: &Series,
neg_order: u32,
pos_order: u32,
) -> SeriesResult<Vec<(i32, Expression)>> {
let mut result = Vec::new();
let denom_lead_power = find_leading_power(denom) as i32;
let denom_lead_coeff = denom
.terms
.iter()
.find(|t| t.power as i32 == denom_lead_power)
.map(|t| &t.coefficient);
if denom_lead_coeff.is_none() {
return Err(SeriesError::DivisionByZero);
}
for num_term in &num.terms {
let result_power = num_term.power as i32 - denom_lead_power;
if result_power >= -(neg_order as i32) && result_power <= pos_order as i32 {
let coeff = match denom_lead_coeff {
Some(Expression::Integer(d)) if *d != 0 => match &num_term.coefficient {
Expression::Integer(n) => {
Expression::Rational(num_rational::Rational64::new(*n, *d))
}
Expression::Rational(r) => {
Expression::Rational(*r / num_rational::Rational64::from(*d))
}
other => Expression::Binary(
BinaryOp::Div,
Box::new(other.clone()),
Box::new(Expression::Integer(*d)),
),
},
_ => num_term.coefficient.clone(),
};
result.push((result_power, coeff));
}
}
Ok(result)
}
fn find_zeros_of_expression(expr: &Expression, var: &Variable) -> Vec<Expression> {
let mut zeros = Vec::new();
match expr {
Expression::Variable(v) if v.name == var.name => {
zeros.push(Expression::Integer(0));
}
Expression::Binary(BinaryOp::Sub, left, right) => {
if matches!(left.as_ref(), Expression::Variable(v) if v.name == var.name) {
zeros.push(right.as_ref().clone());
}
}
Expression::Binary(BinaryOp::Mul, left, right) => {
zeros.extend(find_zeros_of_expression(left, var));
zeros.extend(find_zeros_of_expression(right, var));
}
Expression::Power(base, _) => {
zeros.extend(find_zeros_of_expression(base, var));
}
_ => {}
}
zeros
}
fn get_zero_multiplicity(expr: &Expression, var: &Variable, zero: &Expression) -> u32 {
match expr {
Expression::Power(base, exp) => {
let base_zeros = find_zeros_of_expression(base, var);
if base_zeros.iter().any(|z| expressions_equal(z, zero)) {
extract_integer(exp).unwrap_or(1) as u32
} else {
0
}
}
Expression::Binary(BinaryOp::Mul, left, right) => {
get_zero_multiplicity(left, var, zero) + get_zero_multiplicity(right, var, zero)
}
_ => {
let zeros = find_zeros_of_expression(expr, var);
if zeros.iter().any(|z| expressions_equal(z, zero)) {
1
} else {
0
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsymptoticDirection {
PosInfinity,
NegInfinity,
Zero,
}
impl fmt::Display for AsymptoticDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsymptoticDirection::PosInfinity => write!(f, "x→+∞"),
AsymptoticDirection::NegInfinity => write!(f, "x→-∞"),
AsymptoticDirection::Zero => write!(f, "x→0"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AsymptoticTerm {
pub coefficient: Expression,
pub exponent: Expression,
}
impl AsymptoticTerm {
pub fn new(coefficient: Expression, exponent: Expression) -> Self {
AsymptoticTerm {
coefficient,
exponent,
}
}
pub fn is_zero(&self) -> bool {
matches!(&self.coefficient, Expression::Integer(0))
|| matches!(&self.coefficient, Expression::Float(x) if x.abs() < 1e-15)
}
pub fn evaluate(&self, var: &Variable, point: f64) -> Option<f64> {
let point_expr = Expression::Float(point);
let coeff_result = evaluate_at(&self.coefficient, var, &point_expr).ok()?;
let exp_result = evaluate_at(&self.exponent, var, &point_expr).ok()?;
let coeff = try_expr_to_f64(&coeff_result)?;
let exp = try_expr_to_f64(&exp_result)?;
Some(coeff * point.powf(exp))
}
}
impl fmt::Display for AsymptoticTerm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.exponent {
Expression::Integer(0) => write!(f, "{}", self.coefficient),
Expression::Integer(1) => write!(f, "{}·x", self.coefficient),
Expression::Integer(n) if *n < 0 => {
write!(f, "{}/x^{}", self.coefficient, -n)
}
Expression::Rational(r) => {
write!(f, "{}·x^({})", self.coefficient, r)
}
_ => write!(f, "{}·x^({})", self.coefficient, self.exponent),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BigO {
pub order: Expression,
pub variable: Variable,
}
impl BigO {
pub fn new(order: Expression, variable: Variable) -> Self {
BigO { order, variable }
}
pub fn is_same_order(&self, other: &BigO) -> bool {
self.order == other.order
}
pub fn is_smaller_order(&self, other: &BigO) -> bool {
match (&self.order, &other.order) {
(Expression::Power(_, exp1), Expression::Power(_, exp2)) => {
if let (Some(e1), Some(e2)) = (try_expr_to_f64(exp1), try_expr_to_f64(exp2)) {
return e1 < e2;
}
}
(Expression::Integer(a), Expression::Power(_, _)) => {
return *a == 1;
}
_ => {}
}
false
}
pub fn is_bounded_by(&self, expr: &Expression) -> bool {
expr == &self.order
}
}
impl fmt::Display for BigO {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "O({})", self.order)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AsymptoticSeries {
pub terms: Vec<AsymptoticTerm>,
pub variable: Variable,
pub direction: AsymptoticDirection,
}
impl AsymptoticSeries {
pub fn new(variable: Variable, direction: AsymptoticDirection) -> Self {
AsymptoticSeries {
terms: Vec::new(),
variable,
direction,
}
}
pub fn add_term(&mut self, term: AsymptoticTerm) {
if !term.is_zero() {
self.terms.push(term);
}
}
pub fn dominant_term(&self) -> Option<&AsymptoticTerm> {
self.terms.first()
}
pub fn order_of_magnitude(&self) -> Option<Expression> {
self.dominant_term().map(|t| t.exponent.clone())
}
pub fn with_error_term(&self) -> (Self, BigO) {
let series = self.clone();
let error_order = if let Some(last_term) = self.terms.last() {
match self.direction {
AsymptoticDirection::PosInfinity | AsymptoticDirection::NegInfinity => {
Expression::Binary(
BinaryOp::Sub,
Box::new(last_term.exponent.clone()),
Box::new(Expression::Integer(1)),
)
}
AsymptoticDirection::Zero => Expression::Binary(
BinaryOp::Add,
Box::new(last_term.exponent.clone()),
Box::new(Expression::Integer(1)),
),
}
} else {
Expression::Integer(0)
};
let var_expr = Expression::Variable(self.variable.clone());
let big_o = BigO::new(
Expression::Power(Box::new(var_expr), Box::new(error_order)),
self.variable.clone(),
);
(series, big_o)
}
pub fn evaluate(&self, point: f64) -> Option<f64> {
let mut sum = 0.0;
for term in &self.terms {
sum += term.evaluate(&self.variable, point)?;
}
Some(sum)
}
pub fn to_expression(&self) -> Expression {
if self.terms.is_empty() {
return Expression::Integer(0);
}
let var_expr = Expression::Variable(self.variable.clone());
let mut result: Option<Expression> = None;
for term in &self.terms {
let term_expr = Expression::Binary(
BinaryOp::Mul,
Box::new(term.coefficient.clone()),
Box::new(Expression::Power(
Box::new(var_expr.clone()),
Box::new(term.exponent.clone()),
)),
);
result = Some(match result {
None => term_expr,
Some(acc) => Expression::Binary(BinaryOp::Add, Box::new(acc), Box::new(term_expr)),
});
}
result.unwrap_or(Expression::Integer(0)).simplify()
}
}
impl fmt::Display for AsymptoticSeries {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.terms.is_empty() {
return write!(f, "0");
}
write!(f, "As {}: ", self.direction)?;
for (i, term) in self.terms.iter().enumerate() {
if i > 0 {
write!(f, " + ")?;
}
write!(f, "{}", term)?;
}
Ok(())
}
}
pub fn asymptotic(
expr: &Expression,
var: impl AsRef<str>,
direction: AsymptoticDirection,
num_terms: u32,
) -> SeriesResult<AsymptoticSeries> {
let var_name = var.as_ref();
let variable = Variable::new(var_name);
let mut series = AsymptoticSeries::new(variable.clone(), direction);
extract_asymptotic_terms(expr, &variable, &mut series, num_terms)?;
sort_by_dominance(&mut series.terms, direction);
Ok(series)
}
fn extract_asymptotic_terms(
expr: &Expression,
var: &Variable,
series: &mut AsymptoticSeries,
num_terms: u32,
) -> SeriesResult<()> {
match expr {
Expression::Binary(BinaryOp::Add, left, right) => {
extract_asymptotic_terms(left, var, series, num_terms)?;
extract_asymptotic_terms(right, var, series, num_terms)?;
}
Expression::Binary(BinaryOp::Div, num, den) => {
let is_one = matches!(num.as_ref(), Expression::Integer(1))
|| matches!(num.as_ref(), Expression::Float(f) if (*f - 1.0).abs() < 1e-15);
if is_one {
if let Expression::Variable(v) = den.as_ref() {
if v == var {
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
}
} else if let Expression::Power(base, exp) = den.as_ref() {
if let Expression::Variable(v) = base.as_ref() {
if v == var {
let neg_exp = Expression::Unary(
crate::ast::UnaryOp::Neg,
Box::new(exp.as_ref().clone()),
)
.simplify();
series.add_term(AsymptoticTerm::new(Expression::Integer(1), neg_exp));
}
}
}
} else {
if let Expression::Variable(v) = den.as_ref() {
if v == var {
series.add_term(AsymptoticTerm::new(
num.as_ref().clone(),
Expression::Integer(-1),
));
}
} else if let Expression::Power(base, exp) = den.as_ref() {
if let Expression::Variable(v) = base.as_ref() {
if v == var {
let neg_exp = Expression::Unary(
crate::ast::UnaryOp::Neg,
Box::new(exp.as_ref().clone()),
)
.simplify();
series.add_term(AsymptoticTerm::new(num.as_ref().clone(), neg_exp));
}
}
}
}
}
Expression::Power(base, exp) => {
if let Expression::Variable(v) = base.as_ref() {
if v == var {
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
exp.as_ref().clone(),
));
}
}
}
Expression::Variable(v) if v == var => {
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(1),
));
}
Expression::Integer(n) => {
series.add_term(AsymptoticTerm::new(
Expression::Integer(*n),
Expression::Integer(0),
));
}
Expression::Float(f) => {
series.add_term(AsymptoticTerm::new(
Expression::Float(*f),
Expression::Integer(0),
));
}
_ => {
return Err(SeriesError::CannotExpand(format!(
"Cannot extract asymptotic terms from: {}",
expr
)));
}
}
Ok(())
}
fn sort_by_dominance(terms: &mut Vec<AsymptoticTerm>, direction: AsymptoticDirection) {
terms.sort_by(|a, b| {
let exp_a = try_expr_to_f64(&a.exponent).unwrap_or(0.0);
let exp_b = try_expr_to_f64(&b.exponent).unwrap_or(0.0);
match direction {
AsymptoticDirection::PosInfinity | AsymptoticDirection::NegInfinity => {
exp_b.partial_cmp(&exp_a).unwrap()
}
AsymptoticDirection::Zero => {
exp_a.partial_cmp(&exp_b).unwrap()
}
}
});
}
pub fn limit_via_asymptotic(
expr: &Expression,
var: impl AsRef<str>,
direction: AsymptoticDirection,
) -> Result<crate::limits::LimitResult, crate::limits::LimitError> {
use crate::limits::LimitResult;
let series = asymptotic(expr, var.as_ref(), direction, 5)
.map_err(|e| crate::limits::LimitError::EvaluationError(e.to_string()))?;
if let Some(dominant) = series.dominant_term() {
if let Some(exp) = try_expr_to_f64(&dominant.exponent) {
match direction {
AsymptoticDirection::PosInfinity | AsymptoticDirection::NegInfinity => {
if exp > 0.0 {
if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
if coeff > 0.0 {
return Ok(LimitResult::PositiveInfinity);
} else if coeff < 0.0 {
return Ok(LimitResult::NegativeInfinity);
}
}
} else if exp < 0.0 {
return Ok(LimitResult::Value(0.0));
} else {
if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
return Ok(LimitResult::Value(coeff));
}
}
}
AsymptoticDirection::Zero => {
if exp > 0.0 {
return Ok(LimitResult::Value(0.0));
} else if exp < 0.0 {
if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
if coeff > 0.0 {
return Ok(LimitResult::PositiveInfinity);
} else if coeff < 0.0 {
return Ok(LimitResult::NegativeInfinity);
}
}
} else {
if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
return Ok(LimitResult::Value(coeff));
}
}
}
}
}
}
Err(crate::limits::LimitError::EvaluationError(
"Cannot determine limit from asymptotic expansion".to_string(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_factorial() {
assert_eq!(factorial(0), 1);
assert_eq!(factorial(1), 1);
assert_eq!(factorial(5), 120);
assert_eq!(factorial(10), 3628800);
}
#[test]
fn test_exp_series() {
let x = Variable::new("x");
let series = exp_series(&x, 4);
assert_eq!(series.term_count(), 5);
assert_eq!(series.order, 4);
let term0 = series.get_term(0).unwrap();
assert!(matches!(&term0.coefficient, Expression::Integer(1)));
let term2 = series.get_term(2).unwrap();
if let Expression::Rational(r) = &term2.coefficient {
assert_eq!(*r.numer(), 1);
assert_eq!(*r.denom(), 2);
} else {
panic!("Expected rational coefficient");
}
}
#[test]
fn test_sin_series() {
let x = Variable::new("x");
let series = sin_series(&x, 7);
assert!(series.get_term(0).is_none());
assert!(series.get_term(1).is_some());
assert!(series.get_term(2).is_none());
assert!(series.get_term(3).is_some());
let term1 = series.get_term(1).unwrap();
if let Expression::Rational(r) = &term1.coefficient {
assert_eq!(*r.numer(), 1);
assert_eq!(*r.denom(), 1);
}
let term3 = series.get_term(3).unwrap();
if let Expression::Rational(r) = &term3.coefficient {
assert_eq!(*r.numer(), -1);
assert_eq!(*r.denom(), 6);
}
}
#[test]
fn test_cos_series() {
let x = Variable::new("x");
let series = cos_series(&x, 6);
assert!(series.get_term(0).is_some());
assert!(series.get_term(1).is_none());
assert!(series.get_term(2).is_some());
let term0 = series.get_term(0).unwrap();
if let Expression::Rational(r) = &term0.coefficient {
assert_eq!(*r.numer(), 1);
}
let term2 = series.get_term(2).unwrap();
if let Expression::Rational(r) = &term2.coefficient {
assert_eq!(*r.numer(), -1);
assert_eq!(*r.denom(), 2);
}
}
#[test]
fn test_series_evaluate() {
let x = Variable::new("x");
let series = exp_series(&x, 10);
let result = series.evaluate(0.1).unwrap();
let expected = 0.1_f64.exp();
assert!((result - expected).abs() < 1e-8);
}
#[test]
fn test_sin_series_evaluate() {
let x = Variable::new("x");
let series = sin_series(&x, 11);
let result = series.evaluate(0.5).unwrap();
let expected = 0.5_f64.sin();
assert!((result - expected).abs() < 1e-8);
}
#[test]
fn test_series_to_expression() {
let x = Variable::new("x");
let series = exp_series(&x, 3);
let expr = series.to_expression();
let mut vars = HashMap::new();
vars.insert("x".to_string(), 0.1);
let result = expr.evaluate(&vars).unwrap();
let expected = 1.0 + 0.1 + 0.01 / 2.0 + 0.001 / 6.0;
assert!((result - expected).abs() < 1e-10);
}
#[test]
fn test_series_to_latex() {
let x = Variable::new("x");
let series = sin_series(&x, 5);
let latex = series.to_latex();
assert!(latex.contains("x"));
assert!(latex.contains("x^{3}") || latex.contains("x³"));
}
#[test]
fn test_ln_1_plus_x_series() {
let x = Variable::new("x");
let series = ln_1_plus_x_series(&x, 5);
assert!(series.get_term(0).is_none());
assert!(series.get_term(1).is_some());
let term1 = series.get_term(1).unwrap();
if let Expression::Rational(r) = &term1.coefficient {
assert_eq!(*r.numer(), 1);
assert_eq!(*r.denom(), 1);
}
let term2 = series.get_term(2).unwrap();
if let Expression::Rational(r) = &term2.coefficient {
assert_eq!(*r.numer(), -1);
assert_eq!(*r.denom(), 2);
}
}
#[test]
fn test_arctan_series() {
let x = Variable::new("x");
let series = arctan_series(&x, 11);
let result = series.evaluate(0.3).unwrap();
let expected = 0.3_f64.atan();
assert!((result - expected).abs() < 1e-4);
}
#[test]
fn test_binomial_series() {
let x = Variable::new("x");
let series = binomial_series(&Expression::Integer(2), &x, 5).unwrap();
assert_eq!(series.term_count(), 3);
let series = binomial_series(&Expression::Float(0.5), &x, 5).unwrap();
let result = series.evaluate(0.21).unwrap(); assert!((result - 1.1).abs() < 0.01);
}
#[test]
fn test_taylor_polynomial() {
let x = Variable::new("x");
let expr = Expression::Power(
Box::new(Expression::Variable(x.clone())),
Box::new(Expression::Integer(2)),
);
let series = taylor(&expr, &x, &Expression::Integer(0), 3).unwrap();
let term2 = series.get_term(2);
assert!(term2.is_some());
}
#[test]
fn test_maclaurin_exp() {
let x = Variable::new("x");
let expr = Expression::Function(Function::Exp, vec![Expression::Variable(x.clone())]);
let series = maclaurin(&expr, &x, 5).unwrap();
let expected = exp_series(&x, 5);
assert_eq!(series.term_count(), expected.term_count());
}
#[test]
fn test_singularity_type_display() {
let pole = SingularityType::Pole(2);
assert_eq!(format!("{}", pole), "pole of order 2");
let removable = SingularityType::Removable;
assert_eq!(format!("{}", removable), "removable singularity");
let essential = SingularityType::Essential;
assert_eq!(format!("{}", essential), "essential singularity");
}
#[test]
fn test_laurent_series_creation() {
let z = Variable::new("z");
let center = Expression::Integer(0);
let mut laurent = LaurentSeries::new(z.clone(), center, 1, 1);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 1));
laurent.add_negative_term(Expression::Integer(3), 1);
assert_eq!(laurent.principal_part_order, 1);
assert_eq!(laurent.analytic_part_order, 1);
assert_eq!(laurent.positive_terms.len(), 2);
assert_eq!(laurent.negative_terms.len(), 1);
}
#[test]
fn test_laurent_series_residue() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 1);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(3), 1));
laurent.add_negative_term(Expression::Integer(1), 1);
let res = laurent.residue();
if let Expression::Integer(n) = res {
assert_eq!(n, 1);
} else {
panic!("Expected integer residue, got {:?}", res);
}
}
#[test]
fn test_laurent_series_pole_order() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 3, 0);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_negative_term(Expression::Integer(1), 3);
laurent.add_negative_term(Expression::Integer(2), 1);
assert_eq!(laurent.principal_part_order, 3);
}
#[test]
fn test_laurent_series_principal_part() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 2, 1);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 1));
laurent.add_negative_term(Expression::Integer(2), 2);
laurent.add_negative_term(Expression::Integer(3), 1);
let principal = laurent.principal_part();
assert_eq!(principal.negative_terms.len(), 2);
}
#[test]
fn test_laurent_series_analytic_part() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 2);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(3), 1));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(4), 2));
laurent.add_negative_term(Expression::Integer(1), 1);
let analytic = laurent.analytic_part();
assert_eq!(analytic.term_count(), 3);
}
#[test]
fn test_laurent_series_evaluate() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 1);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 1));
laurent.add_negative_term(Expression::Integer(1), 1);
let result = laurent.evaluate(2.0);
assert!(result.is_some());
assert!((result.unwrap() - 3.5).abs() < 1e-10);
}
#[test]
fn test_laurent_series_evaluate_at_singularity() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 0);
laurent.add_negative_term(Expression::Integer(1), 1);
let result = laurent.evaluate(0.0);
assert!(result.is_none());
}
#[test]
fn test_laurent_series_to_latex() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 1);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(3), 1));
laurent.add_negative_term(Expression::Integer(2), 1);
let latex = laurent.to_latex();
assert!(latex.contains("z^{-1}"));
}
#[test]
fn test_laurent_is_taylor() {
let z = Variable::new("z");
let mut taylor_like = LaurentSeries::new(z.clone(), Expression::Integer(0), 0, 2);
taylor_like.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
taylor_like.add_positive_term(SeriesTerm::new(Expression::Integer(2), 1));
assert!(taylor_like.is_taylor());
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 2);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_negative_term(Expression::Integer(1), 1);
assert!(!laurent.is_taylor());
}
#[test]
fn test_singularity_creation() {
let location = Expression::Integer(0);
let singularity = Singularity {
location: location.clone(),
singularity_type: SingularityType::Pole(2),
};
assert!(matches!(
singularity.singularity_type,
SingularityType::Pole(2)
));
}
#[test]
fn test_find_singularities_simple_pole() {
let z = Variable::new("z");
let expr = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
Box::new(Expression::Variable(z.clone())),
);
let singularities = find_singularities(&expr, &z);
assert!(!singularities.is_empty());
}
#[test]
fn test_pole_order_simple() {
let z = Variable::new("z");
let expr = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
Box::new(Expression::Variable(z.clone())),
);
let order = pole_order(&expr, &z, &Expression::Integer(0));
assert!(order.is_ok());
assert_eq!(order.unwrap(), 1);
}
#[test]
fn test_pole_order_double() {
let z = Variable::new("z");
let z_squared = Expression::Power(
Box::new(Expression::Variable(z.clone())),
Box::new(Expression::Integer(2)),
);
let expr = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
Box::new(z_squared),
);
let order = pole_order(&expr, &z, &Expression::Integer(0));
assert!(order.is_ok());
assert_eq!(order.unwrap(), 2);
}
#[test]
fn test_residue_simple_pole() {
let z = Variable::new("z");
let expr = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
Box::new(Expression::Variable(z.clone())),
);
let res = residue(&expr, &z, &Expression::Integer(0));
assert!(res.is_ok());
let res_val = res.unwrap().simplify();
if let Expression::Integer(n) = res_val {
assert_eq!(n, 1);
} else if let Expression::Float(f) = res_val {
assert!((f - 1.0).abs() < 1e-10);
}
}
#[test]
fn test_laurent_function_simple() {
let z = Variable::new("z");
let expr = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
Box::new(Expression::Variable(z.clone())),
);
let result = laurent(&expr, &z, &Expression::Integer(0), 1, 3);
assert!(result.is_ok());
let laurent_series = result.unwrap();
assert!(!laurent_series.negative_terms.is_empty());
assert_eq!(laurent_series.principal_part_order, 1);
}
#[test]
fn test_laurent_to_taylor() {
let z = Variable::new("z");
let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 0, 2);
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 1));
let taylor_opt = laurent.to_taylor();
assert!(taylor_opt.is_some());
let taylor = taylor_opt.unwrap();
assert_eq!(taylor.term_count(), 2);
}
#[test]
fn test_series_addition() {
let x = Variable::new("x");
let mut s1 = Series::new(x.clone(), Expression::Integer(0), 3);
s1.add_term(SeriesTerm::new(Expression::Integer(1), 0));
s1.add_term(SeriesTerm::new(Expression::Integer(1), 1));
s1.add_term(SeriesTerm::new(Expression::Integer(1), 2));
let mut s2 = Series::new(x.clone(), Expression::Integer(0), 3);
s2.add_term(SeriesTerm::new(Expression::Integer(2), 0));
s2.add_term(SeriesTerm::new(Expression::Integer(3), 1));
s2.add_term(SeriesTerm::new(Expression::Integer(1), 2));
let sum = (s1 + s2).unwrap();
let result = sum.evaluate(0.5).unwrap();
assert!((result - 5.5).abs() < 1e-10);
}
#[test]
fn test_series_subtraction() {
let x = Variable::new("x");
let mut s1 = Series::new(x.clone(), Expression::Integer(0), 2);
s1.add_term(SeriesTerm::new(Expression::Integer(3), 0));
s1.add_term(SeriesTerm::new(Expression::Integer(2), 1));
s1.add_term(SeriesTerm::new(Expression::Integer(1), 2));
let mut s2 = Series::new(x.clone(), Expression::Integer(0), 2);
s2.add_term(SeriesTerm::new(Expression::Integer(1), 0));
s2.add_term(SeriesTerm::new(Expression::Integer(1), 1));
s2.add_term(SeriesTerm::new(Expression::Integer(1), 2));
let diff = (s1 - s2).unwrap();
let result = diff.evaluate(1.0).unwrap();
assert!((result - 3.0).abs() < 1e-10);
}
#[test]
fn test_series_multiplication() {
let x = Variable::new("x");
let mut s1 = Series::new(x.clone(), Expression::Integer(0), 4);
s1.add_term(SeriesTerm::new(Expression::Integer(1), 0));
s1.add_term(SeriesTerm::new(Expression::Integer(1), 1));
let mut s2 = Series::new(x.clone(), Expression::Integer(0), 4);
s2.add_term(SeriesTerm::new(Expression::Integer(1), 0));
s2.add_term(SeriesTerm::new(Expression::Integer(-1), 1));
let product = (s1 * s2).unwrap();
let result = product.evaluate(0.5).unwrap();
assert!((result - 0.75).abs() < 1e-10);
}
#[test]
fn test_series_reciprocal() {
let x = Variable::new("x");
let mut s = Series::new(x.clone(), Expression::Integer(0), 5);
s.add_term(SeriesTerm::new(Expression::Integer(1), 0));
s.add_term(SeriesTerm::new(Expression::Integer(-1), 1));
let recip = s.reciprocal().unwrap();
let result = recip.evaluate(0.5).unwrap();
assert!((result - 1.96875).abs() < 1e-10);
}
#[test]
fn test_series_division() {
let x = Variable::new("x");
let mut num = Series::new(x.clone(), Expression::Integer(0), 4);
num.add_term(SeriesTerm::new(Expression::Integer(1), 0));
num.add_term(SeriesTerm::new(Expression::Integer(-1), 2));
let mut denom = Series::new(x.clone(), Expression::Integer(0), 4);
denom.add_term(SeriesTerm::new(Expression::Integer(1), 0));
denom.add_term(SeriesTerm::new(Expression::Integer(-1), 1));
let quotient = (num / denom).unwrap();
let result = quotient.evaluate(0.5).unwrap();
assert!((result - 1.5).abs() < 0.01);
}
#[test]
fn test_series_differentiate() {
let x = Variable::new("x");
let exp_series = exp_series(&x, 4);
let deriv = exp_series.differentiate();
let result = deriv.evaluate(0.1).unwrap();
let expected = 0.1_f64.exp();
assert!((result - expected).abs() < 0.01);
}
#[test]
fn test_series_integrate() {
let x = Variable::new("x");
let exp_series = exp_series(&x, 3);
let integrated = exp_series.integrate(Expression::Integer(0));
let result = integrated.evaluate(0.1).unwrap();
let expected = 0.1_f64.exp() - 1.0; assert!((result - expected).abs() < 0.01);
}
#[test]
fn test_differentiate_then_integrate() {
let x = Variable::new("x");
let sin_s = sin_series(&x, 7);
let deriv = sin_s.differentiate(); let integrated = deriv.integrate(Expression::Integer(0));
let result = integrated.evaluate(0.5).unwrap();
let expected = 0.5_f64.sin();
assert!((result - expected).abs() < 0.01);
}
#[test]
fn test_exp_times_neg_exp() {
let x = Variable::new("x");
let exp_pos = exp_series(&x, 5);
let mut exp_neg = Series::new(x.clone(), Expression::Integer(0), 5);
for n in 0..=5 {
let sign = if n % 2 == 0 { 1.0 } else { -1.0 };
let coeff = sign / factorial(n) as f64;
exp_neg.add_term(SeriesTerm::new(Expression::Float(coeff), n));
}
let product = (exp_pos * exp_neg).unwrap();
let result = product.evaluate(0.5).unwrap();
assert!((result - 1.0).abs() < 0.01);
}
#[test]
fn test_compose_series_exp_sin() {
let x = Variable::new("x");
let exp_s = exp_series(&x, 5);
let sin_s = sin_series(&x, 5);
let composed = compose_series(&exp_s, &sin_s).unwrap();
let result = composed.evaluate(0.1).unwrap();
let expected = (0.1_f64.sin()).exp();
assert!((result - expected).abs() < 0.01);
}
#[test]
fn test_series_reversion() {
let x = Variable::new("x");
let sin_s = sin_series(&x, 7);
let arcsin_s = reversion(&sin_s).unwrap();
let result = arcsin_s.evaluate(0.5).unwrap();
let expected = 0.5_f64.asin();
assert!((result - expected).abs() < 0.05);
}
#[test]
fn test_asymptotic_direction_display() {
assert_eq!(format!("{}", AsymptoticDirection::PosInfinity), "x→+∞");
assert_eq!(format!("{}", AsymptoticDirection::NegInfinity), "x→-∞");
assert_eq!(format!("{}", AsymptoticDirection::Zero), "x→0");
}
#[test]
fn test_asymptotic_term_creation() {
let term = AsymptoticTerm::new(Expression::Integer(2), Expression::Integer(-1));
assert_eq!(term.coefficient, Expression::Integer(2));
assert_eq!(term.exponent, Expression::Integer(-1));
assert!(!term.is_zero());
let zero_term = AsymptoticTerm::new(Expression::Integer(0), Expression::Integer(1));
assert!(zero_term.is_zero());
}
#[test]
fn test_asymptotic_term_evaluate() {
let x = Variable::new("x");
let term = AsymptoticTerm::new(Expression::Integer(2), Expression::Integer(-1));
let result = term.evaluate(&x, 4.0).unwrap();
assert!((result - 0.5).abs() < 1e-10);
let term2 = AsymptoticTerm::new(Expression::Integer(3), Expression::Integer(2));
let result2 = term2.evaluate(&x, 2.0).unwrap();
assert!((result2 - 12.0).abs() < 1e-10);
}
#[test]
fn test_asymptotic_term_display() {
let term1 = AsymptoticTerm::new(Expression::Integer(2), Expression::Integer(0));
assert_eq!(format!("{}", term1), "2");
let term2 = AsymptoticTerm::new(Expression::Integer(3), Expression::Integer(1));
assert_eq!(format!("{}", term2), "3·x");
let term3 = AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(-2));
assert_eq!(format!("{}", term3), "1/x^2");
}
#[test]
fn test_big_o_creation() {
let x = Variable::new("x");
let order = Expression::Power(
Box::new(Expression::Variable(x.clone())),
Box::new(Expression::Integer(2)),
);
let big_o = BigO::new(order.clone(), x.clone());
assert_eq!(big_o.order, order);
assert_eq!(big_o.variable, x);
}
#[test]
fn test_big_o_is_same_order() {
let x = Variable::new("x");
let order1 = Expression::Power(
Box::new(Expression::Variable(x.clone())),
Box::new(Expression::Integer(2)),
);
let order2 = Expression::Power(
Box::new(Expression::Variable(x.clone())),
Box::new(Expression::Integer(2)),
);
let big_o1 = BigO::new(order1, x.clone());
let big_o2 = BigO::new(order2, x.clone());
assert!(big_o1.is_same_order(&big_o2));
}
#[test]
fn test_big_o_display() {
let x = Variable::new("x");
let order = Expression::Power(
Box::new(Expression::Variable(x.clone())),
Box::new(Expression::Integer(3)),
);
let big_o = BigO::new(order, x);
assert!(format!("{}", big_o).contains("O("));
}
#[test]
fn test_asymptotic_series_creation() {
let x = Variable::new("x");
let series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
assert_eq!(series.variable, x);
assert_eq!(series.direction, AsymptoticDirection::PosInfinity);
assert_eq!(series.terms.len(), 0);
}
#[test]
fn test_asymptotic_series_add_term() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-2),
));
assert_eq!(series.terms.len(), 2);
}
#[test]
fn test_asymptotic_series_dominant_term() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-2),
));
let dominant = series.dominant_term().unwrap();
assert_eq!(dominant.exponent, Expression::Integer(-1));
}
#[test]
fn test_asymptotic_series_order_of_magnitude() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(2),
Expression::Integer(-1),
));
let order = series.order_of_magnitude().unwrap();
assert_eq!(order, Expression::Integer(-1));
}
#[test]
fn test_asymptotic_series_with_error_term() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-2),
));
let (_, big_o) = series.with_error_term();
assert_eq!(big_o.variable, x);
}
#[test]
fn test_asymptotic_series_evaluate() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-2),
));
let result = series.evaluate(2.0).unwrap();
assert!((result - 0.75).abs() < 1e-10);
}
#[test]
fn test_asymptotic_1_over_x() {
use crate::parser::parse_expression;
let expr = parse_expression("1/x").unwrap();
let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
assert_eq!(series.terms.len(), 1);
assert_eq!(series.terms[0].coefficient, Expression::Integer(1));
assert_eq!(series.terms[0].exponent, Expression::Integer(-1));
}
#[test]
fn test_asymptotic_1_over_x_plus_1_over_x2() {
use crate::parser::parse_expression;
let expr = parse_expression("1/x + 1/x^2").unwrap();
let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
assert_eq!(series.terms.len(), 2);
assert_eq!(series.terms[0].exponent, Expression::Integer(-1));
let exp1 = &series.terms[1].exponent;
let exp1_val = try_expr_to_f64(exp1).unwrap();
assert!((exp1_val - (-2.0)).abs() < 1e-10);
}
#[test]
fn test_asymptotic_x_squared_plus_x() {
use crate::parser::parse_expression;
let expr = parse_expression("x^2 + x").unwrap();
let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
let dominant = series.dominant_term().unwrap();
let exp_val = try_expr_to_f64(&dominant.exponent).unwrap();
assert!((exp_val - 2.0).abs() < 1e-10);
}
#[test]
fn test_asymptotic_evaluate_at_point() {
use crate::parser::parse_expression;
let expr = parse_expression("1/x + 1/x^2").unwrap();
let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
let result = series.evaluate(10.0).unwrap();
assert!((result - 0.11).abs() < 1e-10);
}
#[test]
fn test_sort_by_dominance_pos_infinity() {
let _x = Variable::new("x");
let mut terms = vec![
AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(-2)),
AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(-1)),
AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(0)),
];
sort_by_dominance(&mut terms, AsymptoticDirection::PosInfinity);
assert_eq!(terms[0].exponent, Expression::Integer(0));
assert_eq!(terms[1].exponent, Expression::Integer(-1));
assert_eq!(terms[2].exponent, Expression::Integer(-2));
}
#[test]
fn test_sort_by_dominance_zero() {
let _x = Variable::new("x");
let mut terms = vec![
AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(2)),
AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(1)),
AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(0)),
];
sort_by_dominance(&mut terms, AsymptoticDirection::Zero);
assert_eq!(terms[0].exponent, Expression::Integer(0));
assert_eq!(terms[1].exponent, Expression::Integer(1));
assert_eq!(terms[2].exponent, Expression::Integer(2));
}
#[test]
fn test_limit_via_asymptotic_to_zero() {
use crate::limits::LimitResult;
use crate::parser::parse_expression;
let expr = parse_expression("1/x").unwrap();
let result = limit_via_asymptotic(&expr, "x", AsymptoticDirection::PosInfinity).unwrap();
assert_eq!(result, LimitResult::Value(0.0));
}
#[test]
fn test_limit_via_asymptotic_to_infinity() {
use crate::limits::LimitResult;
use crate::parser::parse_expression;
let expr = parse_expression("x^2").unwrap();
let result = limit_via_asymptotic(&expr, "x", AsymptoticDirection::PosInfinity).unwrap();
assert_eq!(result, LimitResult::PositiveInfinity);
}
#[test]
fn test_limit_via_asymptotic_constant() {
use crate::limits::LimitResult;
let expr = Expression::Integer(5);
let result = limit_via_asymptotic(&expr, "x", AsymptoticDirection::PosInfinity).unwrap();
assert_eq!(result, LimitResult::Value(5.0));
}
#[test]
fn test_asymptotic_series_to_expression() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
series.add_term(AsymptoticTerm::new(
Expression::Integer(2),
Expression::Integer(-2),
));
let expr = series.to_expression();
assert!(!matches!(expr, Expression::Integer(0)));
}
#[test]
fn test_asymptotic_series_display() {
let x = Variable::new("x");
let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
series.add_term(AsymptoticTerm::new(
Expression::Integer(1),
Expression::Integer(-1),
));
let display_str = format!("{}", series);
assert!(display_str.contains("x→+∞"));
}
}