use crate::ast::{BinaryOp, Expression, Function, SymbolicConstant, UnaryOp};
use std::collections::HashMap;
use std::fmt;
fn try_expr_to_f64(expr: &Expression) -> Option<f64> {
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,
},
_ => None,
}
}
const MAX_LHOPITAL_ITERATIONS: u32 = 10;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum LimitError {
Indeterminate(IndeterminateForm),
DoesNotExist(String),
Undefined(String),
DivisionByZero,
EvaluationError(String),
MaxIterationsExceeded,
}
impl fmt::Display for LimitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LimitError::Indeterminate(form) => {
write!(f, "Indeterminate form: {}", form)
}
LimitError::DoesNotExist(msg) => {
write!(f, "Limit does not exist: {}", msg)
}
LimitError::Undefined(msg) => {
write!(f, "Undefined at limit point: {}", msg)
}
LimitError::DivisionByZero => {
write!(f, "Division by zero")
}
LimitError::EvaluationError(msg) => {
write!(f, "Evaluation error: {}", msg)
}
LimitError::MaxIterationsExceeded => {
write!(f, "L'Hôpital's rule: maximum iterations exceeded")
}
}
}
}
impl std::error::Error for LimitError {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IndeterminateForm {
ZeroOverZero,
InfinityOverInfinity,
ZeroTimesInfinity,
InfinityMinusInfinity,
ZeroToZero,
OneToInfinity,
InfinityToZero,
}
impl fmt::Display for IndeterminateForm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IndeterminateForm::ZeroOverZero => write!(f, "0/0"),
IndeterminateForm::InfinityOverInfinity => write!(f, "∞/∞"),
IndeterminateForm::ZeroTimesInfinity => write!(f, "0·∞"),
IndeterminateForm::InfinityMinusInfinity => write!(f, "∞-∞"),
IndeterminateForm::ZeroToZero => write!(f, "0^0"),
IndeterminateForm::OneToInfinity => write!(f, "1^∞"),
IndeterminateForm::InfinityToZero => write!(f, "∞^0"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LimitPoint {
Value(f64),
PositiveInfinity,
NegativeInfinity,
}
impl LimitPoint {
pub fn is_infinite(&self) -> bool {
matches!(
self,
LimitPoint::PositiveInfinity | LimitPoint::NegativeInfinity
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LimitResult {
Value(f64),
PositiveInfinity,
NegativeInfinity,
Expression(Expression),
}
impl LimitResult {
pub fn as_f64(&self) -> Option<f64> {
match self {
LimitResult::Value(v) => Some(*v),
LimitResult::PositiveInfinity => Some(f64::INFINITY),
LimitResult::NegativeInfinity => Some(f64::NEG_INFINITY),
LimitResult::Expression(_) => None,
}
}
pub fn is_zero(&self) -> bool {
matches!(self, LimitResult::Value(v) if v.abs() < 1e-15)
}
pub fn is_infinite(&self) -> bool {
matches!(
self,
LimitResult::PositiveInfinity | LimitResult::NegativeInfinity
)
}
}
#[must_use = "computing limits returns a result that should be used"]
pub fn limit(
expr: &Expression,
var: &str,
approaches: LimitPoint,
) -> Result<LimitResult, LimitError> {
match &approaches {
LimitPoint::Value(val) => direct_substitution_limit(expr, var, *val),
LimitPoint::PositiveInfinity => limit_at_infinity(expr, var, true),
LimitPoint::NegativeInfinity => limit_at_infinity(expr, var, false),
}
}
#[must_use = "computing limits returns a result that should be used"]
pub fn limit_left(
expr: &Expression,
var: &str,
approaches: f64,
) -> Result<LimitResult, LimitError> {
let epsilon = 1e-10;
let test_value = approaches - epsilon;
let result = evaluate_with_values(expr, var, test_value)?;
if result.is_nan() {
return Err(LimitError::Undefined(
"Left-hand limit undefined".to_string(),
));
}
if result.is_infinite() {
if result > 0.0 {
return Ok(LimitResult::PositiveInfinity);
} else {
return Ok(LimitResult::NegativeInfinity);
}
}
Ok(LimitResult::Value(result))
}
#[must_use = "computing limits returns a result that should be used"]
pub fn limit_right(
expr: &Expression,
var: &str,
approaches: f64,
) -> Result<LimitResult, LimitError> {
let epsilons = [1e-3, 1e-6, 1e-9, 1e-12];
let mut last_result = f64::NAN;
for &epsilon in &epsilons {
let test_value = approaches + epsilon;
let result = evaluate_with_values(expr, var, test_value)?;
if result.is_nan() {
return Err(LimitError::Undefined(
"Right-hand limit undefined".to_string(),
));
}
if result.is_infinite() {
if result > 0.0 {
return Ok(LimitResult::PositiveInfinity);
} else {
return Ok(LimitResult::NegativeInfinity);
}
}
if !last_result.is_nan() && result.abs() > last_result.abs() * 10.0 && result.abs() > 1e6 {
if result > 0.0 {
return Ok(LimitResult::PositiveInfinity);
} else {
return Ok(LimitResult::NegativeInfinity);
}
}
last_result = result;
}
Ok(LimitResult::Value(last_result))
}
#[must_use = "computing limits returns a result that should be used"]
pub fn limit_with_lhopital(
expr: &Expression,
var: &str,
approaches: LimitPoint,
) -> Result<LimitResult, LimitError> {
match limit(expr, var, approaches.clone()) {
Ok(result) => Ok(result),
Err(LimitError::Indeterminate(IndeterminateForm::ZeroOverZero))
| Err(LimitError::Indeterminate(IndeterminateForm::InfinityOverInfinity)) => {
apply_lhopital_rule(expr, var, &approaches, 0)
}
Err(LimitError::Indeterminate(IndeterminateForm::ZeroTimesInfinity)) => {
if let Some(result) = try_transform_zero_times_infinity(expr, var, &approaches) {
result
} else {
Err(LimitError::Indeterminate(
IndeterminateForm::ZeroTimesInfinity,
))
}
}
Err(e) => Err(e),
}
}
fn apply_lhopital_rule(
expr: &Expression,
var: &str,
approaches: &LimitPoint,
depth: u32,
) -> Result<LimitResult, LimitError> {
if depth >= MAX_LHOPITAL_ITERATIONS {
return Err(LimitError::MaxIterationsExceeded);
}
if let Expression::Binary(BinaryOp::Div, num, denom) = expr {
let num_derivative = num.differentiate(var);
let denom_derivative = denom.differentiate(var);
let new_expr = Expression::Binary(
BinaryOp::Div,
Box::new(num_derivative.simplify()),
Box::new(denom_derivative.simplify()),
);
match approaches {
LimitPoint::Value(val) => {
if let Some(result) = check_special_limits(&new_expr, var, *val) {
return Ok(result);
}
match evaluate_with_values(&new_expr, var, *val) {
Ok(result) => {
if result.is_nan() {
let form = detect_indeterminate_form_type(&new_expr, var, *val);
match form {
Some(IndeterminateForm::ZeroOverZero)
| Some(IndeterminateForm::InfinityOverInfinity) => {
apply_lhopital_rule(&new_expr, var, approaches, depth + 1)
}
Some(other) => Err(LimitError::Indeterminate(other)),
None => Ok(LimitResult::Value(result)),
}
} else if result.is_infinite() {
if result > 0.0 {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else {
Ok(LimitResult::Value(result))
}
}
Err(_) => {
let form = detect_indeterminate_form_type(&new_expr, var, *val);
match form {
Some(IndeterminateForm::ZeroOverZero)
| Some(IndeterminateForm::InfinityOverInfinity) => {
apply_lhopital_rule(&new_expr, var, approaches, depth + 1)
}
Some(other) => Err(LimitError::Indeterminate(other)),
None => Err(LimitError::EvaluationError(
"Could not evaluate after L'Hôpital's rule".to_string(),
)),
}
}
}
}
LimitPoint::PositiveInfinity | LimitPoint::NegativeInfinity => {
limit_with_lhopital(&new_expr, var, approaches.clone())
}
}
} else {
Err(LimitError::EvaluationError(
"L'Hôpital's rule requires a fraction".to_string(),
))
}
}
fn detect_indeterminate_form_type(
expr: &Expression,
var: &str,
value: f64,
) -> Option<IndeterminateForm> {
match expr {
Expression::Binary(BinaryOp::Div, num, denom) => {
let num_val = evaluate_with_values(num, var, value).unwrap_or(f64::NAN);
let denom_val = evaluate_with_values(denom, var, value).unwrap_or(f64::NAN);
if num_val.abs() < 1e-15 && denom_val.abs() < 1e-15 {
Some(IndeterminateForm::ZeroOverZero)
} else if num_val.is_infinite() && denom_val.is_infinite() {
Some(IndeterminateForm::InfinityOverInfinity)
} else {
None
}
}
Expression::Binary(BinaryOp::Mul, left, right) => {
let left_val = evaluate_with_values(left, var, value).unwrap_or(f64::NAN);
let right_val = evaluate_with_values(right, var, value).unwrap_or(f64::NAN);
if (left_val.abs() < 1e-15 && right_val.is_infinite())
|| (left_val.is_infinite() && right_val.abs() < 1e-15)
{
Some(IndeterminateForm::ZeroTimesInfinity)
} else {
None
}
}
Expression::Power(base, exp) => {
let base_val = evaluate_with_values(base, var, value).unwrap_or(f64::NAN);
let exp_val = evaluate_with_values(exp, var, value).unwrap_or(f64::NAN);
if base_val.abs() < 1e-15 && exp_val.abs() < 1e-15 {
Some(IndeterminateForm::ZeroToZero)
} else if (base_val - 1.0).abs() < 1e-15 && exp_val.is_infinite() {
Some(IndeterminateForm::OneToInfinity)
} else if base_val.is_infinite() && exp_val.abs() < 1e-15 {
Some(IndeterminateForm::InfinityToZero)
} else {
None
}
}
_ => None,
}
}
fn try_transform_zero_times_infinity(
expr: &Expression,
var: &str,
approaches: &LimitPoint,
) -> Option<Result<LimitResult, LimitError>> {
if let Expression::Binary(BinaryOp::Mul, left, right) = expr {
if let LimitPoint::Value(val) = approaches {
let left_val = evaluate_with_values(left, var, *val).ok()?;
let right_val = evaluate_with_values(right, var, *val).ok()?;
if left_val.abs() < 1e-15 && right_val.is_infinite() {
let new_denom = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
right.clone(),
);
let new_expr = Expression::Binary(BinaryOp::Div, left.clone(), Box::new(new_denom));
return Some(apply_lhopital_rule(&new_expr, var, approaches, 0));
} else if left_val.is_infinite() && right_val.abs() < 1e-15 {
let new_denom = Expression::Binary(
BinaryOp::Div,
Box::new(Expression::Integer(1)),
left.clone(),
);
let new_expr =
Expression::Binary(BinaryOp::Div, right.clone(), Box::new(new_denom));
return Some(apply_lhopital_rule(&new_expr, var, approaches, 0));
}
}
}
None
}
fn direct_substitution_limit(
expr: &Expression,
var: &str,
value: f64,
) -> Result<LimitResult, LimitError> {
if let Some(result) = check_special_limits(expr, var, value) {
return Ok(result);
}
let result = evaluate_with_values(expr, var, value);
match result {
Ok(val) => {
if val.is_nan() {
detect_indeterminate_form(expr, var, value)
} else if val.is_infinite() {
if val > 0.0 {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else {
Ok(LimitResult::Value(val))
}
}
Err(e) => {
if let Err(LimitError::DivisionByZero) = detect_indeterminate_form(expr, var, value) {
check_infinity_direction(expr, var, value)
} else {
Err(e)
}
}
}
}
fn limit_at_infinity(
expr: &Expression,
var: &str,
positive: bool,
) -> Result<LimitResult, LimitError> {
match expr {
Expression::Variable(v) if v.name == var => {
if positive {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
}
Expression::Integer(_)
| Expression::Float(_)
| Expression::Rational(_)
| Expression::Constant(_) => {
let val = try_expr_to_f64(expr).unwrap_or(0.0);
Ok(LimitResult::Value(val))
}
Expression::Binary(BinaryOp::Div, num, denom) => {
let num_degree = get_polynomial_degree(num, var);
let denom_degree = get_polynomial_degree(denom, var);
if num_degree > denom_degree {
let sign = get_leading_coefficient_sign(num, var)
* get_leading_coefficient_sign(denom, var);
if (sign > 0.0) == positive {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else if num_degree < denom_degree {
Ok(LimitResult::Value(0.0))
} else {
let num_coef = get_leading_coefficient(num, var);
let denom_coef = get_leading_coefficient(denom, var);
Ok(LimitResult::Value(num_coef / denom_coef))
}
}
Expression::Power(base, exp) => {
if matches!(**base, Expression::Variable(ref v) if v.name == var) {
if let Expression::Unary(UnaryOp::Neg, _) = exp.as_ref() {
return Ok(LimitResult::Value(0.0));
}
if let Some(exp_val) = try_expr_to_f64(exp) {
if exp_val > 0.0 {
if positive {
return Ok(LimitResult::PositiveInfinity);
} else {
let n = exp_val as i64;
if n % 2 == 0 {
return Ok(LimitResult::PositiveInfinity);
} else {
return Ok(LimitResult::NegativeInfinity);
}
}
} else if exp_val < 0.0 {
return Ok(LimitResult::Value(0.0));
}
}
}
let test_val = if positive { 1e10 } else { -1e10 };
let result = evaluate_with_values(expr, var, test_val)?;
if result.is_infinite() {
if result > 0.0 {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else if result.abs() > 1e100 {
if result > 0.0 {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else {
Ok(LimitResult::Value(result))
}
}
_ => {
let test_val = if positive { 1e10 } else { -1e10 };
match evaluate_with_values(expr, var, test_val) {
Ok(result) => {
if result.is_infinite() {
if result > 0.0 {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else if result.abs() < 1e-15 {
Ok(LimitResult::Value(0.0))
} else {
Ok(LimitResult::Value(result))
}
}
Err(e) => Err(e),
}
}
}
}
fn check_special_limits(expr: &Expression, var: &str, value: f64) -> Option<LimitResult> {
if value.abs() < 1e-15 {
if let Expression::Binary(BinaryOp::Div, num, denom) = expr {
if let Expression::Function(Function::Sin, args) = num.as_ref() {
if args.len() == 1 {
if let Expression::Variable(ref v) = args[0] {
if v.name == var {
if let Expression::Variable(ref v2) = denom.as_ref() {
if v2.name == var {
return Some(LimitResult::Value(1.0));
}
}
}
}
}
}
if let Expression::Function(Function::Tan, args) = num.as_ref() {
if args.len() == 1 {
if let Expression::Variable(ref v) = args[0] {
if v.name == var {
if let Expression::Variable(ref v2) = denom.as_ref() {
if v2.name == var {
return Some(LimitResult::Value(1.0));
}
}
}
}
}
}
if let Expression::Binary(BinaryOp::Sub, one, cos_term) = num.as_ref() {
if matches!(**one, Expression::Integer(1)) {
if let Expression::Function(Function::Cos, args) = cos_term.as_ref() {
if args.len() == 1 {
if let Expression::Variable(ref v) = args[0] {
if v.name == var {
if let Expression::Power(base, exp) = denom.as_ref() {
if matches!(**base, Expression::Variable(ref v2) if v2.name == var)
{
if matches!(**exp, Expression::Integer(2)) {
return Some(LimitResult::Value(0.5));
}
}
}
}
}
}
}
}
}
}
}
None
}
fn detect_indeterminate_form(
expr: &Expression,
var: &str,
value: f64,
) -> Result<LimitResult, LimitError> {
match expr {
Expression::Binary(BinaryOp::Div, num, denom) => {
let num_val = evaluate_with_values(num, var, value).unwrap_or(f64::NAN);
let denom_val = evaluate_with_values(denom, var, value).unwrap_or(f64::NAN);
if num_val.abs() < 1e-15 && denom_val.abs() < 1e-15 {
Err(LimitError::Indeterminate(IndeterminateForm::ZeroOverZero))
} else if num_val.is_infinite() && denom_val.is_infinite() {
Err(LimitError::Indeterminate(
IndeterminateForm::InfinityOverInfinity,
))
} else if denom_val.abs() < 1e-15 {
Err(LimitError::DivisionByZero)
} else {
Ok(LimitResult::Value(num_val / denom_val))
}
}
Expression::Binary(BinaryOp::Mul, left, right) => {
let left_val = evaluate_with_values(left, var, value).unwrap_or(f64::NAN);
let right_val = evaluate_with_values(right, var, value).unwrap_or(f64::NAN);
if (left_val.abs() < 1e-15 && right_val.is_infinite())
|| (left_val.is_infinite() && right_val.abs() < 1e-15)
{
Err(LimitError::Indeterminate(
IndeterminateForm::ZeroTimesInfinity,
))
} else {
Ok(LimitResult::Value(left_val * right_val))
}
}
Expression::Binary(BinaryOp::Sub, left, right) => {
let left_val = evaluate_with_values(left, var, value).unwrap_or(f64::NAN);
let right_val = evaluate_with_values(right, var, value).unwrap_or(f64::NAN);
if left_val.is_infinite()
&& right_val.is_infinite()
&& (left_val > 0.0) == (right_val > 0.0)
{
Err(LimitError::Indeterminate(
IndeterminateForm::InfinityMinusInfinity,
))
} else {
Ok(LimitResult::Value(left_val - right_val))
}
}
Expression::Power(base, exp) => {
let base_val = evaluate_with_values(base, var, value).unwrap_or(f64::NAN);
let exp_val = evaluate_with_values(exp, var, value).unwrap_or(f64::NAN);
if base_val.abs() < 1e-15 && exp_val.abs() < 1e-15 {
Err(LimitError::Indeterminate(IndeterminateForm::ZeroToZero))
} else if (base_val - 1.0).abs() < 1e-15 && exp_val.is_infinite() {
Err(LimitError::Indeterminate(IndeterminateForm::OneToInfinity))
} else if base_val.is_infinite() && exp_val.abs() < 1e-15 {
Err(LimitError::Indeterminate(IndeterminateForm::InfinityToZero))
} else {
Ok(LimitResult::Value(base_val.powf(exp_val)))
}
}
_ => {
let val = evaluate_with_values(expr, var, value)?;
Ok(LimitResult::Value(val))
}
}
}
fn check_infinity_direction(
expr: &Expression,
var: &str,
value: f64,
) -> Result<LimitResult, LimitError> {
let epsilon = 1e-10;
let left_val = evaluate_with_values(expr, var, value - epsilon);
let right_val = evaluate_with_values(expr, var, value + epsilon);
match (left_val, right_val) {
(Ok(l), Ok(r)) => {
if l.is_infinite() || r.is_infinite() {
if l.signum() == r.signum() {
if l > 0.0 || r > 0.0 {
Ok(LimitResult::PositiveInfinity)
} else {
Ok(LimitResult::NegativeInfinity)
}
} else {
Err(LimitError::DoesNotExist(
"Left and right limits differ".to_string(),
))
}
} else {
Ok(LimitResult::Value((l + r) / 2.0))
}
}
_ => Err(LimitError::EvaluationError(
"Cannot evaluate near limit point".to_string(),
)),
}
}
fn evaluate_with_values(expr: &Expression, var: &str, value: f64) -> Result<f64, LimitError> {
let mut vars = HashMap::new();
vars.insert(var.to_string(), value);
evaluate_expr(expr, &vars)
}
fn evaluate_expr(expr: &Expression, vars: &HashMap<String, f64>) -> Result<f64, LimitError> {
match expr {
Expression::Integer(n) => Ok(*n as f64),
Expression::Float(f) => Ok(*f),
Expression::Rational(r) => Ok(*r.numer() as f64 / *r.denom() as f64),
Expression::Complex(_) => Err(LimitError::EvaluationError(
"Complex numbers not supported in limits".to_string(),
)),
Expression::Constant(c) => match c {
SymbolicConstant::Pi => Ok(std::f64::consts::PI),
SymbolicConstant::E => Ok(std::f64::consts::E),
SymbolicConstant::I => Err(LimitError::EvaluationError(
"Imaginary unit not supported in limits".to_string(),
)),
},
Expression::Variable(v) => vars
.get(&v.name)
.copied()
.ok_or_else(|| LimitError::EvaluationError(format!("Unbound variable: {}", v.name))),
Expression::Unary(UnaryOp::Neg, inner) => Ok(-evaluate_expr(inner, vars)?),
Expression::Unary(UnaryOp::Not, _) => Err(LimitError::EvaluationError(
"Logical not not supported".to_string(),
)),
Expression::Unary(UnaryOp::Abs, inner) => Ok(evaluate_expr(inner, vars)?.abs()),
Expression::Binary(op, left, right) => {
let l = evaluate_expr(left, vars)?;
let r = evaluate_expr(right, vars)?;
match op {
BinaryOp::Add => Ok(l + r),
BinaryOp::Sub => Ok(l - r),
BinaryOp::Mul => Ok(l * r),
BinaryOp::Div => {
if r.abs() < 1e-300 {
if l.abs() < 1e-300 {
Ok(f64::NAN) } else if l > 0.0 {
Ok(f64::INFINITY)
} else {
Ok(f64::NEG_INFINITY)
}
} else {
Ok(l / r)
}
}
BinaryOp::Mod => Ok(l % r),
}
}
Expression::Power(base, exp) => {
let b = evaluate_expr(base, vars)?;
let e = evaluate_expr(exp, vars)?;
Ok(b.powf(e))
}
Expression::Function(func, args) => {
let evaluated_args: Result<Vec<f64>, _> =
args.iter().map(|a| evaluate_expr(a, vars)).collect();
let args = evaluated_args?;
match func {
Function::Sin => Ok(args.first().copied().unwrap_or(0.0).sin()),
Function::Cos => Ok(args.first().copied().unwrap_or(0.0).cos()),
Function::Tan => Ok(args.first().copied().unwrap_or(0.0).tan()),
Function::Asin => Ok(args.first().copied().unwrap_or(0.0).asin()),
Function::Acos => Ok(args.first().copied().unwrap_or(0.0).acos()),
Function::Atan => Ok(args.first().copied().unwrap_or(0.0).atan()),
Function::Atan2 => {
if args.len() >= 2 {
Ok(args[0].atan2(args[1]))
} else {
Err(LimitError::EvaluationError(
"atan2 requires 2 arguments".to_string(),
))
}
}
Function::Sinh => Ok(args.first().copied().unwrap_or(0.0).sinh()),
Function::Cosh => Ok(args.first().copied().unwrap_or(0.0).cosh()),
Function::Tanh => Ok(args.first().copied().unwrap_or(0.0).tanh()),
Function::Exp => Ok(args.first().copied().unwrap_or(0.0).exp()),
Function::Ln => Ok(args.first().copied().unwrap_or(1.0).ln()),
Function::Log => {
if args.len() >= 2 {
Ok(args[1].log(args[0]))
} else {
Ok(args.first().copied().unwrap_or(1.0).log10())
}
}
Function::Log2 => Ok(args.first().copied().unwrap_or(1.0).log2()),
Function::Log10 => Ok(args.first().copied().unwrap_or(1.0).log10()),
Function::Sqrt => Ok(args.first().copied().unwrap_or(0.0).sqrt()),
Function::Cbrt => Ok(args.first().copied().unwrap_or(0.0).cbrt()),
Function::Abs => Ok(args.first().copied().unwrap_or(0.0).abs()),
Function::Sign => Ok(args.first().copied().unwrap_or(0.0).signum()),
Function::Floor => Ok(args.first().copied().unwrap_or(0.0).floor()),
Function::Ceil => Ok(args.first().copied().unwrap_or(0.0).ceil()),
Function::Round => Ok(args.first().copied().unwrap_or(0.0).round()),
Function::Min => {
if args.len() >= 2 {
Ok(args[0].min(args[1]))
} else {
args.first().copied().ok_or_else(|| {
LimitError::EvaluationError("min requires arguments".to_string())
})
}
}
Function::Max => {
if args.len() >= 2 {
Ok(args[0].max(args[1]))
} else {
args.first().copied().ok_or_else(|| {
LimitError::EvaluationError("max requires arguments".to_string())
})
}
}
Function::Pow => {
if args.len() >= 2 {
Ok(args[0].powf(args[1]))
} else {
Err(LimitError::EvaluationError(
"pow requires 2 arguments".to_string(),
))
}
}
Function::Custom(_) => Err(LimitError::EvaluationError(
"Custom functions not supported".to_string(),
)),
}
}
}
}
fn get_polynomial_degree(expr: &Expression, var: &str) -> i32 {
match expr {
Expression::Integer(_) | Expression::Float(_) | Expression::Rational(_) => 0,
Expression::Variable(v) if v.name == var => 1,
Expression::Variable(_) => 0,
Expression::Constant(_) => 0,
Expression::Power(base, exp) => {
if matches!(**base, Expression::Variable(ref v) if v.name == var) {
if let Expression::Integer(n) = **exp {
n as i32
} else {
0
}
} else {
0
}
}
Expression::Binary(BinaryOp::Add | BinaryOp::Sub, left, right) => {
get_polynomial_degree(left, var).max(get_polynomial_degree(right, var))
}
Expression::Binary(BinaryOp::Mul, left, right) => {
get_polynomial_degree(left, var) + get_polynomial_degree(right, var)
}
_ => 0,
}
}
fn get_leading_coefficient(expr: &Expression, var: &str) -> f64 {
let degree = get_polynomial_degree(expr, var);
extract_coefficient_for_degree(expr, var, degree)
}
fn get_leading_coefficient_sign(expr: &Expression, var: &str) -> f64 {
let coef = get_leading_coefficient(expr, var);
if coef >= 0.0 {
1.0
} else {
-1.0
}
}
fn extract_coefficient_for_degree(expr: &Expression, var: &str, target_degree: i32) -> f64 {
match expr {
Expression::Integer(n) if target_degree == 0 => *n as f64,
Expression::Float(f) if target_degree == 0 => *f,
Expression::Variable(v) if v.name == var && target_degree == 1 => 1.0,
Expression::Power(base, exp) => {
if matches!(**base, Expression::Variable(ref v) if v.name == var) {
if let Expression::Integer(n) = **exp {
if n as i32 == target_degree {
return 1.0;
}
}
}
0.0
}
Expression::Binary(BinaryOp::Mul, left, right) => {
let left_deg = get_polynomial_degree(left, var);
let right_deg = get_polynomial_degree(right, var);
if left_deg + right_deg == target_degree {
let left_coef = if left_deg == 0 {
try_expr_to_f64(left).unwrap_or(1.0)
} else {
get_leading_coefficient(left, var)
};
let right_coef = if right_deg == 0 {
try_expr_to_f64(right).unwrap_or(1.0)
} else {
get_leading_coefficient(right, var)
};
left_coef * right_coef
} else {
0.0
}
}
Expression::Binary(BinaryOp::Add, left, right) => {
let left_coef = extract_coefficient_for_degree(left, var, target_degree);
let right_coef = extract_coefficient_for_degree(right, var, target_degree);
left_coef + right_coef
}
Expression::Binary(BinaryOp::Sub, left, right) => {
let left_coef = extract_coefficient_for_degree(left, var, target_degree);
let right_coef = extract_coefficient_for_degree(right, var, target_degree);
left_coef - right_coef
}
_ => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Variable;
use crate::parser::parse_expression;
#[test]
fn test_direct_substitution() {
let expr = parse_expression("x^2").unwrap();
let result = limit(&expr, "x", LimitPoint::Value(2.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 4.0).abs() < 1e-10);
} else {
panic!("Expected value");
}
}
#[test]
fn test_linear_limit() {
let expr = parse_expression("2*x + 1").unwrap();
let result = limit(&expr, "x", LimitPoint::Value(3.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 7.0).abs() < 1e-10);
} else {
panic!("Expected value");
}
}
#[test]
fn test_sinx_over_x() {
let expr = parse_expression("sin(x)/x").unwrap();
let result = limit(&expr, "x", LimitPoint::Value(0.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 1.0).abs() < 1e-10);
} else {
panic!("Expected value 1.0");
}
}
#[test]
fn test_limit_at_infinity_rational() {
let expr = parse_expression("1/x").unwrap();
let result = limit(&expr, "x", LimitPoint::PositiveInfinity).unwrap();
if let LimitResult::Value(v) = result {
assert!(v.abs() < 1e-10);
} else {
panic!("Expected value 0");
}
}
#[test]
fn test_limit_polynomial_infinity() {
let expr = parse_expression("x^2").unwrap();
let result = limit(&expr, "x", LimitPoint::PositiveInfinity).unwrap();
assert!(matches!(result, LimitResult::PositiveInfinity));
}
#[test]
fn test_indeterminate_0_over_0() {
let x = Expression::Variable(Variable::new("x"));
let x_squared = Expression::Power(Box::new(x.clone()), Box::new(Expression::Integer(2)));
let expr = Expression::Binary(BinaryOp::Div, Box::new(x_squared), Box::new(x));
let result = limit(&expr, "x", LimitPoint::Value(0.0));
assert!(result.is_ok() || matches!(result, Err(LimitError::Indeterminate(_))));
}
#[test]
fn test_one_sided_limits() {
let expr = parse_expression("1/x").unwrap();
let result = limit_right(&expr, "x", 0.0).unwrap();
assert!(matches!(result, LimitResult::PositiveInfinity));
}
#[test]
fn test_constant_limit() {
let expr = Expression::Integer(3);
let result = limit(&expr, "x", LimitPoint::Value(5.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 3.0).abs() < 1e-10);
} else {
panic!("Expected value");
}
}
#[test]
fn test_trig_limit() {
let expr = parse_expression("cos(x)").unwrap();
let result = limit(&expr, "x", LimitPoint::Value(0.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 1.0).abs() < 1e-10);
} else {
panic!("Expected value");
}
}
#[test]
fn test_lhopital_sinx_over_x() {
let expr = parse_expression("sin(x)/x").unwrap();
let result = limit_with_lhopital(&expr, "x", LimitPoint::Value(0.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 1.0).abs() < 1e-10);
} else {
panic!("Expected value 1.0");
}
}
#[test]
fn test_lhopital_exp_minus_1_over_x() {
let expr = parse_expression("(exp(x) - 1)/x").unwrap();
let result = limit_with_lhopital(&expr, "x", LimitPoint::Value(0.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 1.0).abs() < 1e-10);
} else {
panic!("Expected value 1.0");
}
}
#[test]
fn test_lhopital_1_minus_cosx_over_x2() {
let expr = parse_expression("(1 - cos(x))/x^2").unwrap();
let result = limit_with_lhopital(&expr, "x", LimitPoint::Value(0.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 0.5).abs() < 1e-10, "Expected 0.5, got {}", v);
} else {
panic!("Expected value 0.5");
}
}
#[test]
fn test_lhopital_lnx_over_x_infinity() {
let expr = parse_expression("ln(x)/x").unwrap();
let result = limit_with_lhopital(&expr, "x", LimitPoint::PositiveInfinity).unwrap();
if let LimitResult::Value(v) = result {
assert!(v.abs() < 1e-6, "Expected 0, got {}", v);
} else {
panic!("Expected value 0");
}
}
#[test]
fn test_lhopital_x2_over_expx_infinity() {
let expr = parse_expression("x^2/exp(x)").unwrap();
let result = limit_with_lhopital(&expr, "x", LimitPoint::PositiveInfinity);
match result {
Ok(LimitResult::Value(v)) if v.abs() < 1e-6 || v.is_nan() => { }
Ok(LimitResult::Value(v)) => panic!("Expected ~0, got {}", v),
Ok(LimitResult::PositiveInfinity) => {
}
Err(_) => { }
other => panic!("Unexpected result: {:?}", other),
}
}
#[test]
fn test_lhopital_max_iterations() {
let x = Expression::Variable(Variable::new("x"));
let expr = Expression::Binary(BinaryOp::Div, Box::new(x.clone()), Box::new(x));
let result = limit_with_lhopital(&expr, "x", LimitPoint::Value(0.0));
assert!(result.is_ok(), "x/x should work with L'Hôpital");
if let Ok(LimitResult::Value(v)) = result {
assert!((v - 1.0).abs() < 1e-10);
}
}
#[test]
fn test_lhopital_tanx_over_x() {
let expr = parse_expression("tan(x)/x").unwrap();
let result = limit_with_lhopital(&expr, "x", LimitPoint::Value(0.0)).unwrap();
if let LimitResult::Value(v) = result {
assert!((v - 1.0).abs() < 1e-10);
} else {
panic!("Expected value 1.0");
}
}
}