thales/
series.rs

1//! Taylor and Maclaurin series expansion module.
2//!
3//! This module provides symbolic Taylor and Maclaurin series expansion capabilities
4//! for mathematical expressions. It supports:
5//!
6//! - Taylor series expansion around arbitrary center points
7//! - Maclaurin series (Taylor series centered at 0)
8//! - Built-in series for common functions (exp, sin, cos, ln, arctan)
9//! - Polynomial output and LaTeX generation
10//! - Numerical evaluation of series approximations
11//!
12//! # Examples
13//!
14//! ```rust
15//! use thales::series::{taylor, maclaurin};
16//! use thales::ast::{Expression, Variable};
17//!
18//! // Maclaurin series of e^x to 4th order
19//! let x = Variable::new("x");
20//! let expr = Expression::Function(thales::ast::Function::Exp, vec![Expression::Variable(x.clone())]);
21//! let series = maclaurin(&expr, &x, 4).unwrap();
22//! // Result: 1 + x + x²/2 + x³/6 + x⁴/24 + O(x⁵)
23//! ```
24
25use crate::ast::{BinaryOp, Expression, Function, Variable};
26use std::collections::HashMap;
27use std::fmt;
28
29/// Try to convert a simple expression to f64.
30/// Returns None for expressions that can't be directly converted.
31fn try_expr_to_f64(expr: &Expression) -> Option<f64> {
32    use crate::ast::{SymbolicConstant, UnaryOp};
33    match expr {
34        Expression::Integer(n) => Some(*n as f64),
35        Expression::Float(f) => Some(*f),
36        Expression::Rational(r) => Some(*r.numer() as f64 / *r.denom() as f64),
37        Expression::Constant(c) => match c {
38            SymbolicConstant::Pi => Some(std::f64::consts::PI),
39            SymbolicConstant::E => Some(std::f64::consts::E),
40            SymbolicConstant::I => None,
41        },
42        Expression::Unary(op, inner) => {
43            let val = try_expr_to_f64(inner)?;
44            match op {
45                UnaryOp::Neg => Some(-val),
46                UnaryOp::Abs => Some(val.abs()),
47                _ => None,
48            }
49        }
50        Expression::Binary(op, left, right) => {
51            let l = try_expr_to_f64(left)?;
52            let r = try_expr_to_f64(right)?;
53            match op {
54                BinaryOp::Add => Some(l + r),
55                BinaryOp::Sub => Some(l - r),
56                BinaryOp::Mul => Some(l * r),
57                BinaryOp::Div if r.abs() > 1e-15 => Some(l / r),
58                _ => None,
59            }
60        }
61        Expression::Power(base, exp) => {
62            let b = try_expr_to_f64(base)?;
63            let e = try_expr_to_f64(exp)?;
64            Some(b.powf(e))
65        }
66        _ => None,
67    }
68}
69
70/// Error types for series expansion operations.
71#[derive(Debug, Clone, PartialEq)]
72#[non_exhaustive]
73pub enum SeriesError {
74    /// The expression cannot be expanded as a series.
75    CannotExpand(String),
76    /// The center point is invalid for this expansion.
77    InvalidCenter(String),
78    /// Division by zero encountered during expansion.
79    DivisionByZero,
80    /// Differentiation failed during coefficient computation.
81    DerivativeFailed(String),
82    /// Evaluation at the center point failed.
83    EvaluationFailed(String),
84    /// Order must be non-negative.
85    InvalidOrder(String),
86}
87
88impl fmt::Display for SeriesError {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        match self {
91            SeriesError::CannotExpand(msg) => write!(f, "Cannot expand expression: {}", msg),
92            SeriesError::InvalidCenter(msg) => write!(f, "Invalid center point: {}", msg),
93            SeriesError::DivisionByZero => write!(f, "Division by zero in series expansion"),
94            SeriesError::DerivativeFailed(msg) => write!(f, "Differentiation failed: {}", msg),
95            SeriesError::EvaluationFailed(msg) => write!(f, "Evaluation at center failed: {}", msg),
96            SeriesError::InvalidOrder(msg) => write!(f, "Invalid order: {}", msg),
97        }
98    }
99}
100
101impl std::error::Error for SeriesError {}
102
103/// Result type for series operations.
104pub type SeriesResult<T> = Result<T, SeriesError>;
105
106/// A single term in a power series: coefficient × (x - center)^power.
107#[derive(Debug, Clone, PartialEq)]
108pub struct SeriesTerm {
109    /// The coefficient of this term (can be symbolic).
110    pub coefficient: Expression,
111    /// The power of (x - center) for this term.
112    pub power: u32,
113}
114
115impl SeriesTerm {
116    /// Create a new series term.
117    pub fn new(coefficient: Expression, power: u32) -> Self {
118        SeriesTerm { coefficient, power }
119    }
120
121    /// Check if this term has a zero coefficient.
122    pub fn is_zero(&self) -> bool {
123        matches!(&self.coefficient, Expression::Integer(0))
124            || matches!(&self.coefficient, Expression::Float(x) if x.abs() < 1e-15)
125    }
126}
127
128impl fmt::Display for SeriesTerm {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        if self.power == 0 {
131            write!(f, "{}", self.coefficient)
132        } else if self.power == 1 {
133            write!(f, "{}·x", self.coefficient)
134        } else {
135            write!(f, "{}·x^{}", self.coefficient, self.power)
136        }
137    }
138}
139
140/// Remainder term representation for truncated series.
141#[derive(Debug, Clone, PartialEq)]
142pub enum RemainderTerm {
143    /// Lagrange remainder with explicit error bound.
144    Lagrange {
145        /// Upper bound on the remainder.
146        bound: Expression,
147        /// Order of the remainder term.
148        order: u32,
149    },
150    /// Big-O notation for asymptotic remainder.
151    BigO {
152        /// Order of the remainder (e.g., O(x^n) has order n).
153        order: u32,
154    },
155}
156
157impl RemainderTerm {
158    /// Get the order of the remainder term.
159    pub fn order(&self) -> u32 {
160        match self {
161            RemainderTerm::Lagrange { order, .. } => *order,
162            RemainderTerm::BigO { order } => *order,
163        }
164    }
165
166    /// Convert to LaTeX representation.
167    pub fn to_latex(&self) -> String {
168        match self {
169            RemainderTerm::Lagrange { bound, order } => {
170                format!("R_{{{}}}(x) \\leq {}", order, bound)
171            }
172            RemainderTerm::BigO { order } => {
173                format!("O(x^{{{}}})", order)
174            }
175        }
176    }
177}
178
179impl fmt::Display for RemainderTerm {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        match self {
182            RemainderTerm::Lagrange { order, .. } => write!(f, "R_{}(x)", order),
183            RemainderTerm::BigO { order } => write!(f, "O(x^{})", order),
184        }
185    }
186}
187
188/// A complete power series representation.
189#[derive(Debug, Clone, PartialEq)]
190pub struct Series {
191    /// The terms of the series, in ascending order of power.
192    pub terms: Vec<SeriesTerm>,
193    /// The center point of the expansion.
194    pub center: Expression,
195    /// The variable of expansion.
196    pub variable: Variable,
197    /// The highest order computed.
198    pub order: u32,
199    /// Optional remainder term.
200    pub remainder: Option<RemainderTerm>,
201}
202
203impl Series {
204    /// Create a new empty series.
205    pub fn new(variable: Variable, center: Expression, order: u32) -> Self {
206        Series {
207            terms: Vec::new(),
208            center,
209            variable,
210            order,
211            remainder: None,
212        }
213    }
214
215    /// Add a term to the series.
216    pub fn add_term(&mut self, term: SeriesTerm) {
217        if !term.is_zero() {
218            self.terms.push(term);
219        }
220    }
221
222    /// Set the remainder term.
223    pub fn set_remainder(&mut self, remainder: RemainderTerm) {
224        self.remainder = Some(remainder);
225    }
226
227    /// Get the number of non-zero terms.
228    pub fn term_count(&self) -> usize {
229        self.terms.len()
230    }
231
232    /// Get a specific term by power.
233    pub fn get_term(&self, power: u32) -> Option<&SeriesTerm> {
234        self.terms.iter().find(|t| t.power == power)
235    }
236
237    /// Convert the series to a polynomial Expression.
238    pub fn to_expression(&self) -> Expression {
239        if self.terms.is_empty() {
240            return Expression::Integer(0);
241        }
242
243        let var_expr = Expression::Variable(self.variable.clone());
244        let is_centered_at_zero = matches!(&self.center, Expression::Integer(0))
245            || matches!(&self.center, Expression::Float(x) if x.abs() < 1e-15);
246
247        let mut result: Option<Expression> = None;
248
249        for term in &self.terms {
250            // Build (x - center)^power
251            let power_base = if is_centered_at_zero {
252                var_expr.clone()
253            } else {
254                Expression::Binary(
255                    BinaryOp::Sub,
256                    Box::new(var_expr.clone()),
257                    Box::new(self.center.clone()),
258                )
259            };
260
261            let term_expr = if term.power == 0 {
262                term.coefficient.clone()
263            } else if term.power == 1 {
264                Expression::Binary(
265                    BinaryOp::Mul,
266                    Box::new(term.coefficient.clone()),
267                    Box::new(power_base),
268                )
269            } else {
270                Expression::Binary(
271                    BinaryOp::Mul,
272                    Box::new(term.coefficient.clone()),
273                    Box::new(Expression::Power(
274                        Box::new(power_base),
275                        Box::new(Expression::Integer(term.power as i64)),
276                    )),
277                )
278            };
279
280            result = Some(match result {
281                None => term_expr,
282                Some(acc) => Expression::Binary(BinaryOp::Add, Box::new(acc), Box::new(term_expr)),
283            });
284        }
285
286        result.unwrap_or(Expression::Integer(0)).simplify()
287    }
288
289    /// Convert the series to LaTeX representation.
290    pub fn to_latex(&self) -> String {
291        if self.terms.is_empty() {
292            return "0".to_string();
293        }
294
295        let is_centered_at_zero = matches!(&self.center, Expression::Integer(0));
296        let var_name = &self.variable.name;
297
298        let mut parts = Vec::new();
299        for (i, term) in self.terms.iter().enumerate() {
300            let coeff_str = format_coefficient_latex(&term.coefficient);
301
302            let term_str = if term.power == 0 {
303                coeff_str
304            } else {
305                let var_part = if is_centered_at_zero {
306                    if term.power == 1 {
307                        var_name.clone()
308                    } else {
309                        format!("{}^{{{}}}", var_name, term.power)
310                    }
311                } else {
312                    if term.power == 1 {
313                        format!("({} - {})", var_name, self.center)
314                    } else {
315                        format!("({} - {})^{{{}}}", var_name, self.center, term.power)
316                    }
317                };
318
319                if coeff_str == "1" {
320                    var_part
321                } else if coeff_str == "-1" {
322                    format!("-{}", var_part)
323                } else {
324                    format!("{} {}", coeff_str, var_part)
325                }
326            };
327
328            if i == 0 {
329                parts.push(term_str);
330            } else {
331                // Handle sign for subsequent terms
332                if term_str.starts_with('-') {
333                    parts.push(format!(" - {}", &term_str[1..]));
334                } else {
335                    parts.push(format!(" + {}", term_str));
336                }
337            }
338        }
339
340        let mut result = parts.join("");
341
342        if let Some(ref remainder) = self.remainder {
343            result.push_str(&format!(" + {}", remainder.to_latex()));
344        }
345
346        result
347    }
348
349    /// Numerically evaluate the series at a point.
350    pub fn evaluate(&self, x: f64) -> Option<f64> {
351        let center_val = self.center.evaluate(&HashMap::new())?;
352        let dx = x - center_val;
353
354        let mut sum = 0.0;
355        for term in &self.terms {
356            let coeff = term.coefficient.evaluate(&HashMap::new())?;
357            sum += coeff * dx.powi(term.power as i32);
358        }
359
360        Some(sum)
361    }
362
363    /// Get coefficient for a given power as f64, or 0 if not present.
364    fn coeff_f64(&self, power: u32) -> f64 {
365        self.get_term(power)
366            .and_then(|t| try_expr_to_f64(&t.coefficient))
367            .unwrap_or(0.0)
368    }
369
370    /// Compute the reciprocal of this series (1/S).
371    /// Requires a_0 ≠ 0.
372    pub fn reciprocal(&self) -> SeriesResult<Series> {
373        // Get a_0
374        let a0 = self.coeff_f64(0);
375        if a0.abs() < 1e-15 {
376            return Err(SeriesError::CannotExpand(
377                "Cannot compute reciprocal: constant term is zero".into(),
378            ));
379        }
380
381        let mut result = Series::new(self.variable.clone(), self.center.clone(), self.order);
382
383        // b_0 = 1/a_0
384        result.add_term(SeriesTerm::new(Expression::Float(1.0 / a0), 0));
385
386        // b_n = -(1/a_0) * sum_{k=1}^n a_k * b_{n-k}
387        for n in 1..=self.order {
388            let mut sum = 0.0;
389            for k in 1..=n {
390                let a_k = self.coeff_f64(k);
391                let b_n_k = result.coeff_f64(n - k);
392                sum += a_k * b_n_k;
393            }
394            let b_n = -sum / a0;
395            if b_n.abs() > 1e-15 {
396                result.add_term(SeriesTerm::new(Expression::Float(b_n), n));
397            }
398        }
399
400        Ok(result)
401    }
402
403    /// Term-by-term differentiation of the series.
404    /// d/dx[sum a_n * (x-c)^n] = sum n * a_n * (x-c)^{n-1}
405    pub fn differentiate(&self) -> Series {
406        let new_order = if self.order > 0 { self.order - 1 } else { 0 };
407        let mut result = Series::new(self.variable.clone(), self.center.clone(), new_order);
408
409        for term in &self.terms {
410            if term.power > 0 {
411                // n * a_n -> coefficient of (x-c)^{n-1}
412                let new_coeff = Expression::Binary(
413                    BinaryOp::Mul,
414                    Box::new(Expression::Integer(term.power as i64)),
415                    Box::new(term.coefficient.clone()),
416                )
417                .simplify();
418                result.add_term(SeriesTerm::new(new_coeff, term.power - 1));
419            }
420        }
421
422        result
423    }
424
425    /// Term-by-term integration of the series.
426    /// integral[sum a_n * (x-c)^n] = C + sum a_n * (x-c)^{n+1} / (n+1)
427    pub fn integrate(&self, constant: Expression) -> Series {
428        let mut result = Series::new(self.variable.clone(), self.center.clone(), self.order + 1);
429
430        // Add the integration constant as the x^0 term
431        result.add_term(SeriesTerm::new(constant, 0));
432
433        for term in &self.terms {
434            // a_n / (n+1) -> coefficient of (x-c)^{n+1}
435            let new_coeff = Expression::Binary(
436                BinaryOp::Div,
437                Box::new(term.coefficient.clone()),
438                Box::new(Expression::Integer((term.power + 1) as i64)),
439            )
440            .simplify();
441            result.add_term(SeriesTerm::new(new_coeff, term.power + 1));
442        }
443
444        result
445    }
446}
447
448// Series arithmetic operations using std::ops traits
449use std::ops::{Add, Div, Mul, Sub};
450
451impl Add for Series {
452    type Output = SeriesResult<Series>;
453
454    fn add(self, rhs: Series) -> SeriesResult<Series> {
455        // Check that centers match
456        if self.center != rhs.center {
457            return Err(SeriesError::InvalidCenter(
458                "Cannot add series with different centers".into(),
459            ));
460        }
461        if self.variable.name != rhs.variable.name {
462            return Err(SeriesError::CannotExpand(
463                "Cannot add series with different variables".into(),
464            ));
465        }
466
467        let min_order = self.order.min(rhs.order);
468        let mut result = Series::new(self.variable.clone(), self.center.clone(), min_order);
469
470        // Collect coefficients by power
471        let mut coeffs: HashMap<u32, Expression> = HashMap::new();
472
473        for term in &self.terms {
474            if term.power <= min_order {
475                coeffs.insert(term.power, term.coefficient.clone());
476            }
477        }
478
479        for term in &rhs.terms {
480            if term.power <= min_order {
481                let coeff = coeffs.entry(term.power).or_insert(Expression::Integer(0));
482                *coeff = Expression::Binary(
483                    BinaryOp::Add,
484                    Box::new(coeff.clone()),
485                    Box::new(term.coefficient.clone()),
486                )
487                .simplify();
488            }
489        }
490
491        for (power, coeff) in coeffs {
492            result.add_term(SeriesTerm::new(coeff, power));
493        }
494
495        // Sort terms by power
496        result.terms.sort_by_key(|t| t.power);
497
498        Ok(result)
499    }
500}
501
502impl Sub for Series {
503    type Output = SeriesResult<Series>;
504
505    fn sub(self, rhs: Series) -> SeriesResult<Series> {
506        // Check that centers match
507        if self.center != rhs.center {
508            return Err(SeriesError::InvalidCenter(
509                "Cannot subtract series with different centers".into(),
510            ));
511        }
512        if self.variable.name != rhs.variable.name {
513            return Err(SeriesError::CannotExpand(
514                "Cannot subtract series with different variables".into(),
515            ));
516        }
517
518        let min_order = self.order.min(rhs.order);
519        let mut result = Series::new(self.variable.clone(), self.center.clone(), min_order);
520
521        // Collect coefficients by power
522        let mut coeffs: HashMap<u32, Expression> = HashMap::new();
523
524        for term in &self.terms {
525            if term.power <= min_order {
526                coeffs.insert(term.power, term.coefficient.clone());
527            }
528        }
529
530        for term in &rhs.terms {
531            if term.power <= min_order {
532                let coeff = coeffs.entry(term.power).or_insert(Expression::Integer(0));
533                *coeff = Expression::Binary(
534                    BinaryOp::Sub,
535                    Box::new(coeff.clone()),
536                    Box::new(term.coefficient.clone()),
537                )
538                .simplify();
539            }
540        }
541
542        for (power, coeff) in coeffs {
543            result.add_term(SeriesTerm::new(coeff, power));
544        }
545
546        // Sort terms by power
547        result.terms.sort_by_key(|t| t.power);
548
549        Ok(result)
550    }
551}
552
553impl Mul for Series {
554    type Output = SeriesResult<Series>;
555
556    fn mul(self, rhs: Series) -> SeriesResult<Series> {
557        // Check that centers match
558        if self.center != rhs.center {
559            return Err(SeriesError::InvalidCenter(
560                "Cannot multiply series with different centers".into(),
561            ));
562        }
563        if self.variable.name != rhs.variable.name {
564            return Err(SeriesError::CannotExpand(
565                "Cannot multiply series with different variables".into(),
566            ));
567        }
568
569        let min_order = self.order.min(rhs.order);
570        let mut result = Series::new(self.variable.clone(), self.center.clone(), min_order);
571
572        // Cauchy product: c_n = sum_{k=0}^n a_k * b_{n-k}
573        let mut coeffs: HashMap<u32, Expression> = HashMap::new();
574
575        for term_a in &self.terms {
576            for term_b in &rhs.terms {
577                let new_power = term_a.power + term_b.power;
578                if new_power <= min_order {
579                    let product = Expression::Binary(
580                        BinaryOp::Mul,
581                        Box::new(term_a.coefficient.clone()),
582                        Box::new(term_b.coefficient.clone()),
583                    )
584                    .simplify();
585
586                    let coeff = coeffs.entry(new_power).or_insert(Expression::Integer(0));
587                    *coeff = Expression::Binary(
588                        BinaryOp::Add,
589                        Box::new(coeff.clone()),
590                        Box::new(product),
591                    )
592                    .simplify();
593                }
594            }
595        }
596
597        for (power, coeff) in coeffs {
598            result.add_term(SeriesTerm::new(coeff, power));
599        }
600
601        // Sort terms by power
602        result.terms.sort_by_key(|t| t.power);
603
604        Ok(result)
605    }
606}
607
608impl Div for Series {
609    type Output = SeriesResult<Series>;
610
611    fn div(self, rhs: Series) -> SeriesResult<Series> {
612        // S1 / S2 = S1 * (1/S2)
613        let reciprocal = rhs.reciprocal()?;
614        self * reciprocal
615    }
616}
617
618/// Compose two series: outer(inner(x)).
619/// Generally requires inner(c) = 0 for convergence (where c is the center).
620pub fn compose_series(outer: &Series, inner: &Series) -> SeriesResult<Series> {
621    // Check that the inner series starts with 0 at its center
622    if outer.center != inner.center {
623        return Err(SeriesError::InvalidCenter(
624            "Cannot compose series with different centers".into(),
625        ));
626    }
627    if outer.variable.name != inner.variable.name {
628        return Err(SeriesError::CannotExpand(
629            "Cannot compose series with different variables".into(),
630        ));
631    }
632
633    // For composition, inner(c) should be 0 (or at least the a_0 term should be zero)
634    let inner_a0 = inner.coeff_f64(0);
635    if inner_a0.abs() > 1e-15 {
636        return Err(SeriesError::CannotExpand(
637            "Cannot compose series: inner series must have zero constant term".into(),
638        ));
639    }
640
641    let order = outer.order.min(inner.order);
642    let mut result = Series::new(outer.variable.clone(), outer.center.clone(), order);
643
644    // S_outer(S_inner(x)) = sum_{n=0}^N a_n * S_inner(x)^n
645    // We compute powers of inner series incrementally
646    let mut inner_powers: Vec<Series> = Vec::new();
647
648    // inner^0 = 1
649    let mut inner_pow_0 = Series::new(inner.variable.clone(), inner.center.clone(), order);
650    inner_pow_0.add_term(SeriesTerm::new(Expression::Integer(1), 0));
651    inner_powers.push(inner_pow_0);
652
653    // Precompute powers of inner up to order
654    for _ in 1..=order {
655        let prev = inner_powers.last().unwrap().clone();
656        let next = (prev * inner.clone())?;
657        inner_powers.push(next);
658    }
659
660    // Now sum: sum a_n * inner^n
661    let mut coeffs: HashMap<u32, Expression> = HashMap::new();
662
663    for term in &outer.terms {
664        if term.power as usize <= inner_powers.len() - 1 {
665            let inner_pow = &inner_powers[term.power as usize];
666            for inner_term in &inner_pow.terms {
667                if inner_term.power <= order {
668                    let contribution = Expression::Binary(
669                        BinaryOp::Mul,
670                        Box::new(term.coefficient.clone()),
671                        Box::new(inner_term.coefficient.clone()),
672                    )
673                    .simplify();
674
675                    let coeff = coeffs
676                        .entry(inner_term.power)
677                        .or_insert(Expression::Integer(0));
678                    *coeff = Expression::Binary(
679                        BinaryOp::Add,
680                        Box::new(coeff.clone()),
681                        Box::new(contribution),
682                    )
683                    .simplify();
684                }
685            }
686        }
687    }
688
689    for (power, coeff) in coeffs {
690        result.add_term(SeriesTerm::new(coeff, power));
691    }
692
693    result.terms.sort_by_key(|t| t.power);
694
695    Ok(result)
696}
697
698/// Compute the compositional inverse (reversion) of a series.
699/// Find T such that S(T(x)) = x.
700/// Requires a_0 = 0 and a_1 ≠ 0.
701pub fn reversion(series: &Series) -> SeriesResult<Series> {
702    let a0 = series.coeff_f64(0);
703    let a1 = series.coeff_f64(1);
704
705    if a0.abs() > 1e-15 {
706        return Err(SeriesError::CannotExpand(
707            "Cannot compute reversion: constant term must be zero".into(),
708        ));
709    }
710    if a1.abs() < 1e-15 {
711        return Err(SeriesError::CannotExpand(
712            "Cannot compute reversion: linear coefficient must be non-zero".into(),
713        ));
714    }
715
716    let order = series.order;
717    let mut result = Series::new(series.variable.clone(), series.center.clone(), order);
718
719    // b_1 = 1/a_1
720    let b1 = 1.0 / a1;
721    result.add_term(SeriesTerm::new(Expression::Float(b1), 1));
722
723    // Use Lagrange inversion formula for higher coefficients
724    // For n >= 2: b_n can be computed from the coefficients of S and lower b_k
725    // This is a simplified Newton iteration approach
726
727    for n in 2..=order {
728        // Compute b_n using the implicit equation:
729        // sum_{k=1}^n a_k * [T^k]_{coeff of x^n} = delta_{n,1}
730        // Since T(x) = sum b_j x^j, we need [T^k]_n
731        let mut sum = 0.0;
732
733        for k in 1..=n {
734            // Compute the coefficient of x^n in T(x)^k
735            let tk_coeff_n = compute_power_coeff(&result, k, n);
736            let a_k = series.coeff_f64(k);
737            sum += a_k * tk_coeff_n;
738        }
739
740        // sum = delta_{n,1} = 0 for n > 1
741        // a_1 * b_n + contribution_from_lower = 0
742        // b_n = -contribution_from_lower / a_1
743
744        // The contribution from a_1 * b_n needs to be separated
745        let contribution_without_b_n = sum - a1 * 0.0; // b_n hasn't been added yet
746        let b_n = -contribution_without_b_n / a1;
747
748        // Actually, we need to reconsider - the sum already doesn't include b_n
749        // So b_n = (0 - sum) / a1, but sum already accounts for known terms only
750        // This needs more careful derivation
751
752        if b_n.abs() > 1e-15 {
753            result.add_term(SeriesTerm::new(Expression::Float(b_n), n));
754        }
755    }
756
757    result.terms.sort_by_key(|t| t.power);
758
759    Ok(result)
760}
761
762/// Helper: compute coefficient of x^n in T^k where T is a series.
763fn compute_power_coeff(series: &Series, k: u32, n: u32) -> f64 {
764    if k == 0 {
765        return if n == 0 { 1.0 } else { 0.0 };
766    }
767    if k == 1 {
768        return series.coeff_f64(n);
769    }
770
771    // For k > 1, use convolution
772    // [T^k]_n = sum_{j=0}^n [T]_j * [T^{k-1}]_{n-j}
773    let mut sum = 0.0;
774    for j in 0..=n {
775        let t_j = series.coeff_f64(j);
776        let t_k1_nj = compute_power_coeff(series, k - 1, n - j);
777        sum += t_j * t_k1_nj;
778    }
779    sum
780}
781
782/// Format a coefficient for LaTeX output.
783fn format_coefficient_latex(expr: &Expression) -> String {
784    match expr {
785        Expression::Integer(n) => n.to_string(),
786        Expression::Float(x) => {
787            if (x - x.round()).abs() < 1e-10 {
788                format!("{}", x.round() as i64)
789            } else {
790                format!("{:.6}", x)
791            }
792        }
793        Expression::Rational(r) => {
794            format!("\\frac{{{}}}{{{}}}", r.numer(), r.denom())
795        }
796        _ => format!("{}", expr),
797    }
798}
799
800/// Compute n! (factorial).
801pub fn factorial(n: u32) -> u64 {
802    if n <= 1 {
803        1
804    } else {
805        (2..=n as u64).product()
806    }
807}
808
809/// Compute n! as an Expression.
810pub fn factorial_expr(n: u32) -> Expression {
811    Expression::Integer(factorial(n) as i64)
812}
813
814/// Evaluate an expression at a specific value of a variable.
815pub fn evaluate_at(
816    expr: &Expression,
817    var: &Variable,
818    value: &Expression,
819) -> SeriesResult<Expression> {
820    // Create substitution
821    let substituted = substitute(expr, var, value);
822
823    // Try to simplify to a constant
824    let simplified = substituted.simplify();
825
826    // Check if it's a numeric result
827    if let Some(val) = simplified.evaluate(&HashMap::new()) {
828        if val.is_nan() {
829            return Err(SeriesError::EvaluationFailed(format!(
830                "Expression evaluates to NaN at {} = {}",
831                var.name, value
832            )));
833        }
834        if val.is_infinite() {
835            return Err(SeriesError::EvaluationFailed(format!(
836                "Expression evaluates to infinity at {} = {}",
837                var.name, value
838            )));
839        }
840        return Ok(Expression::Float(val));
841    }
842
843    Ok(simplified)
844}
845
846/// Substitute a variable with an expression.
847fn substitute(expr: &Expression, var: &Variable, value: &Expression) -> Expression {
848    match expr {
849        Expression::Variable(v) if v.name == var.name => value.clone(),
850        Expression::Binary(op, left, right) => Expression::Binary(
851            *op,
852            Box::new(substitute(left, var, value)),
853            Box::new(substitute(right, var, value)),
854        ),
855        Expression::Unary(op, inner) => {
856            Expression::Unary(*op, Box::new(substitute(inner, var, value)))
857        }
858        Expression::Function(func, args) => Expression::Function(
859            func.clone(),
860            args.iter().map(|a| substitute(a, var, value)).collect(),
861        ),
862        Expression::Power(base, exp) => Expression::Power(
863            Box::new(substitute(base, var, value)),
864            Box::new(substitute(exp, var, value)),
865        ),
866        _ => expr.clone(),
867    }
868}
869
870/// Compute the nth derivative of an expression.
871pub fn compute_nth_derivative(
872    expr: &Expression,
873    var: &Variable,
874    n: u32,
875) -> SeriesResult<Expression> {
876    let mut result = expr.clone();
877    for _ in 0..n {
878        result = result.differentiate(&var.name);
879        result = result.simplify();
880    }
881    Ok(result)
882}
883
884/// Compute the Taylor series of an expression around a center point.
885///
886/// # Arguments
887/// * `expr` - The expression to expand
888/// * `var` - The variable of expansion
889/// * `center` - The center point of expansion
890/// * `order` - The number of terms to compute (0 through order)
891///
892/// # Returns
893/// A `Series` containing the Taylor expansion terms and remainder.
894pub fn taylor(
895    expr: &Expression,
896    var: &Variable,
897    center: &Expression,
898    order: u32,
899) -> SeriesResult<Series> {
900    // First, check if we can use a known series
901    if let Some(series) = try_known_series(expr, var, center, order) {
902        return Ok(series);
903    }
904
905    let mut series = Series::new(var.clone(), center.clone(), order);
906
907    for n in 0..=order {
908        // Compute nth derivative
909        let nth_deriv = compute_nth_derivative(expr, var, n)?;
910
911        // Evaluate at center
912        let deriv_at_center = evaluate_at(&nth_deriv, var, center)?;
913
914        // Compute coefficient: f^(n)(center) / n!
915        let n_fact = factorial(n) as i64;
916        let coefficient = if n_fact == 1 {
917            deriv_at_center
918        } else {
919            Expression::Binary(
920                BinaryOp::Div,
921                Box::new(deriv_at_center),
922                Box::new(Expression::Integer(n_fact)),
923            )
924            .simplify()
925        };
926
927        // Add term if non-zero
928        let term = SeriesTerm::new(coefficient, n);
929        series.add_term(term);
930    }
931
932    // Set remainder
933    series.set_remainder(RemainderTerm::BigO { order: order + 1 });
934
935    Ok(series)
936}
937
938/// Compute the Maclaurin series (Taylor series centered at 0).
939pub fn maclaurin(expr: &Expression, var: &Variable, order: u32) -> SeriesResult<Series> {
940    taylor(expr, var, &Expression::Integer(0), order)
941}
942
943/// Try to match the expression to a known series for efficiency.
944fn try_known_series(
945    expr: &Expression,
946    var: &Variable,
947    center: &Expression,
948    order: u32,
949) -> Option<Series> {
950    // Only handle Maclaurin (center = 0) for built-in series
951    if !matches!(center, Expression::Integer(0)) {
952        return None;
953    }
954
955    match expr {
956        Expression::Function(Function::Exp, args) if args.len() == 1 => {
957            if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
958                return Some(exp_series(var, order));
959            }
960        }
961        Expression::Function(Function::Sin, args) if args.len() == 1 => {
962            if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
963                return Some(sin_series(var, order));
964            }
965        }
966        Expression::Function(Function::Cos, args) if args.len() == 1 => {
967            if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
968                return Some(cos_series(var, order));
969            }
970        }
971        Expression::Function(Function::Ln, args) if args.len() == 1 => {
972            // Check for ln(1 + x)
973            if let Expression::Binary(BinaryOp::Add, left, right) = &args[0] {
974                if matches!(**left, Expression::Integer(1))
975                    && matches!(**right, Expression::Variable(ref v) if v.name == var.name)
976                {
977                    return Some(ln_1_plus_x_series(var, order));
978                }
979            }
980        }
981        Expression::Function(Function::Atan, args) if args.len() == 1 => {
982            if matches!(&args[0], Expression::Variable(v) if v.name == var.name) {
983                return Some(arctan_series(var, order));
984            }
985        }
986        _ => {}
987    }
988
989    None
990}
991
992/// Maclaurin series for e^x: sum(x^n / n!) for n = 0 to order.
993pub fn exp_series(var: &Variable, order: u32) -> Series {
994    let mut series = Series::new(var.clone(), Expression::Integer(0), order);
995
996    for n in 0..=order {
997        let coeff = if n == 0 {
998            Expression::Integer(1)
999        } else {
1000            let n_fact = factorial(n);
1001            Expression::Rational(num_rational::Ratio::new(1, n_fact as i64))
1002        };
1003        series.add_term(SeriesTerm::new(coeff, n));
1004    }
1005
1006    series.set_remainder(RemainderTerm::BigO { order: order + 1 });
1007    series
1008}
1009
1010/// Maclaurin series for sin(x): x - x³/3! + x⁵/5! - ...
1011pub fn sin_series(var: &Variable, order: u32) -> Series {
1012    let mut series = Series::new(var.clone(), Expression::Integer(0), order);
1013
1014    let mut n = 0u32;
1015    while 2 * n + 1 <= order {
1016        let power = 2 * n + 1;
1017        let sign = if n % 2 == 0 { 1i64 } else { -1i64 };
1018        let fact = factorial(power) as i64;
1019        let coeff = Expression::Rational(num_rational::Ratio::new(sign, fact));
1020        series.add_term(SeriesTerm::new(coeff, power));
1021        n += 1;
1022    }
1023
1024    series.set_remainder(RemainderTerm::BigO { order: order + 1 });
1025    series
1026}
1027
1028/// Maclaurin series for cos(x): 1 - x²/2! + x⁴/4! - ...
1029pub fn cos_series(var: &Variable, order: u32) -> Series {
1030    let mut series = Series::new(var.clone(), Expression::Integer(0), order);
1031
1032    let mut n = 0u32;
1033    while 2 * n <= order {
1034        let power = 2 * n;
1035        let sign = if n % 2 == 0 { 1i64 } else { -1i64 };
1036        let fact = if power == 0 {
1037            1
1038        } else {
1039            factorial(power) as i64
1040        };
1041        let coeff = Expression::Rational(num_rational::Ratio::new(sign, fact));
1042        series.add_term(SeriesTerm::new(coeff, power));
1043        n += 1;
1044    }
1045
1046    series.set_remainder(RemainderTerm::BigO { order: order + 1 });
1047    series
1048}
1049
1050/// Maclaurin series for ln(1+x): x - x²/2 + x³/3 - x⁴/4 + ...
1051pub fn ln_1_plus_x_series(var: &Variable, order: u32) -> Series {
1052    let mut series = Series::new(var.clone(), Expression::Integer(0), order);
1053
1054    for n in 1..=order {
1055        let sign = if n % 2 == 1 { 1i64 } else { -1i64 };
1056        let coeff = Expression::Rational(num_rational::Ratio::new(sign, n as i64));
1057        series.add_term(SeriesTerm::new(coeff, n));
1058    }
1059
1060    series.set_remainder(RemainderTerm::BigO { order: order + 1 });
1061    series
1062}
1063
1064/// Maclaurin series for arctan(x): x - x³/3 + x⁵/5 - x⁷/7 + ...
1065pub fn arctan_series(var: &Variable, order: u32) -> Series {
1066    let mut series = Series::new(var.clone(), Expression::Integer(0), order);
1067
1068    let mut n = 0u32;
1069    while 2 * n + 1 <= order {
1070        let power = 2 * n + 1;
1071        let sign = if n % 2 == 0 { 1i64 } else { -1i64 };
1072        let coeff = Expression::Rational(num_rational::Ratio::new(sign, power as i64));
1073        series.add_term(SeriesTerm::new(coeff, power));
1074        n += 1;
1075    }
1076
1077    series.set_remainder(RemainderTerm::BigO { order: order + 1 });
1078    series
1079}
1080
1081/// Binomial series for (1+x)^a: sum(C(a,n) * x^n).
1082/// Only works for symbolic or numeric exponents.
1083pub fn binomial_series(exponent: &Expression, var: &Variable, order: u32) -> SeriesResult<Series> {
1084    let mut series = Series::new(var.clone(), Expression::Integer(0), order);
1085
1086    // For now, only handle numeric exponents
1087    let a = match exponent.evaluate(&HashMap::new()) {
1088        Some(val) => val,
1089        None => {
1090            return Err(SeriesError::CannotExpand(
1091                "Binomial series requires numeric exponent".to_string(),
1092            ))
1093        }
1094    };
1095
1096    // Compute binomial coefficients C(a, n) = a*(a-1)*...*(a-n+1) / n!
1097    let mut binom_coeff = 1.0;
1098    for n in 0..=order {
1099        if n > 0 {
1100            binom_coeff *= (a - (n as f64 - 1.0)) / (n as f64);
1101        }
1102
1103        if binom_coeff.abs() > 1e-15 {
1104            let coeff = if (binom_coeff - binom_coeff.round()).abs() < 1e-10 {
1105                Expression::Integer(binom_coeff.round() as i64)
1106            } else {
1107                Expression::Float(binom_coeff)
1108            };
1109            series.add_term(SeriesTerm::new(coeff, n));
1110        }
1111
1112        // For positive integer a, series terminates
1113        if a.fract() == 0.0 && a >= 0.0 && n as f64 >= a {
1114            break;
1115        }
1116    }
1117
1118    // Only add remainder if series doesn't terminate
1119    if a.fract() != 0.0 || a < 0.0 {
1120        series.set_remainder(RemainderTerm::BigO { order: order + 1 });
1121    }
1122
1123    Ok(series)
1124}
1125
1126// ============================================================================
1127// Laurent Series
1128// ============================================================================
1129
1130/// Type of singularity at a point.
1131#[derive(Debug, Clone, PartialEq)]
1132pub enum SingularityType {
1133    /// A removable singularity (e.g., sin(x)/x at x=0).
1134    Removable,
1135    /// A pole of given order (e.g., 1/x^n has a pole of order n at x=0).
1136    Pole(u32),
1137    /// An essential singularity (e.g., e^(1/x) at x=0).
1138    Essential,
1139}
1140
1141impl fmt::Display for SingularityType {
1142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143        match self {
1144            SingularityType::Removable => write!(f, "removable singularity"),
1145            SingularityType::Pole(order) => write!(f, "pole of order {}", order),
1146            SingularityType::Essential => write!(f, "essential singularity"),
1147        }
1148    }
1149}
1150
1151/// A singularity of a function.
1152#[derive(Debug, Clone)]
1153pub struct Singularity {
1154    /// Location of the singularity.
1155    pub location: Expression,
1156    /// Type of the singularity.
1157    pub singularity_type: SingularityType,
1158}
1159
1160impl Singularity {
1161    /// Create a new singularity.
1162    pub fn new(location: Expression, singularity_type: SingularityType) -> Self {
1163        Self {
1164            location,
1165            singularity_type,
1166        }
1167    }
1168
1169    /// Check if this is a pole.
1170    pub fn is_pole(&self) -> bool {
1171        matches!(self.singularity_type, SingularityType::Pole(_))
1172    }
1173
1174    /// Get the pole order if this is a pole.
1175    pub fn pole_order(&self) -> Option<u32> {
1176        match self.singularity_type {
1177            SingularityType::Pole(order) => Some(order),
1178            _ => None,
1179        }
1180    }
1181}
1182
1183/// A Laurent series representation with both positive and negative powers.
1184///
1185/// Laurent series: Σ (a_n * (x - c)^n) for n from -M to N
1186///
1187/// where M is the principal_part_order (negative powers) and N is analytic_part_order.
1188#[derive(Debug, Clone)]
1189pub struct LaurentSeries {
1190    /// Terms with non-negative powers (the analytic part): a_0, a_1, a_2, ...
1191    pub positive_terms: Vec<SeriesTerm>,
1192    /// Terms with negative powers (the principal part): a_{-1}, a_{-2}, ...
1193    /// Stored as positive powers for convenience (index 0 = coefficient of (x-c)^{-1}).
1194    pub negative_terms: Vec<SeriesTerm>,
1195    /// Center of the expansion.
1196    pub center: Expression,
1197    /// Variable of expansion.
1198    pub variable: Variable,
1199    /// Highest negative power (order of principal part, e.g., 2 for 1/(x-c)^2).
1200    pub principal_part_order: u32,
1201    /// Highest positive power in the analytic part.
1202    pub analytic_part_order: u32,
1203}
1204
1205impl LaurentSeries {
1206    /// Create a new empty Laurent series.
1207    pub fn new(variable: Variable, center: Expression, neg_order: u32, pos_order: u32) -> Self {
1208        LaurentSeries {
1209            positive_terms: Vec::new(),
1210            negative_terms: Vec::new(),
1211            center,
1212            variable,
1213            principal_part_order: neg_order,
1214            analytic_part_order: pos_order,
1215        }
1216    }
1217
1218    /// Add a term with positive power.
1219    pub fn add_positive_term(&mut self, term: SeriesTerm) {
1220        if !term.is_zero() {
1221            self.positive_terms.push(term);
1222        }
1223    }
1224
1225    /// Add a term with negative power.
1226    /// The power should be positive (representing the absolute value of the negative exponent).
1227    pub fn add_negative_term(&mut self, coefficient: Expression, neg_power: u32) {
1228        if neg_power > 0 {
1229            let term = SeriesTerm::new(coefficient, neg_power);
1230            if !term.is_zero() {
1231                self.negative_terms.push(term);
1232            }
1233        }
1234    }
1235
1236    /// Get the residue (coefficient of (x - center)^{-1}).
1237    pub fn residue(&self) -> Expression {
1238        self.negative_terms
1239            .iter()
1240            .find(|t| t.power == 1)
1241            .map(|t| t.coefficient.clone())
1242            .unwrap_or(Expression::Integer(0))
1243    }
1244
1245    /// Get the principal part (negative power terms only).
1246    pub fn principal_part(&self) -> LaurentSeries {
1247        let mut result = LaurentSeries::new(
1248            self.variable.clone(),
1249            self.center.clone(),
1250            self.principal_part_order,
1251            0,
1252        );
1253        result.negative_terms = self.negative_terms.clone();
1254        result
1255    }
1256
1257    /// Get the analytic part (non-negative power terms only) as a regular Series.
1258    pub fn analytic_part(&self) -> Series {
1259        let mut result = Series::new(
1260            self.variable.clone(),
1261            self.center.clone(),
1262            self.analytic_part_order,
1263        );
1264        for term in &self.positive_terms {
1265            result.add_term(term.clone());
1266        }
1267        result
1268    }
1269
1270    /// Check if this is actually a Taylor series (no negative powers).
1271    pub fn is_taylor(&self) -> bool {
1272        self.negative_terms.is_empty()
1273    }
1274
1275    /// Convert to a Taylor series if there are no negative powers.
1276    pub fn to_taylor(&self) -> Option<Series> {
1277        if self.is_taylor() {
1278            Some(self.analytic_part())
1279        } else {
1280            None
1281        }
1282    }
1283
1284    /// Evaluate the Laurent series at a point.
1285    pub fn evaluate(&self, x: f64) -> Option<f64> {
1286        let center_val = try_expr_to_f64(&self.center)?;
1287        let dx = x - center_val;
1288
1289        if dx.abs() < 1e-15 && !self.negative_terms.is_empty() {
1290            // At the singularity with poles - undefined
1291            return None;
1292        }
1293
1294        let mut sum = 0.0;
1295
1296        // Add positive power terms
1297        for term in &self.positive_terms {
1298            let coeff = try_expr_to_f64(&term.coefficient)?;
1299            sum += coeff * dx.powi(term.power as i32);
1300        }
1301
1302        // Add negative power terms
1303        for term in &self.negative_terms {
1304            let coeff = try_expr_to_f64(&term.coefficient)?;
1305            sum += coeff / dx.powi(term.power as i32);
1306        }
1307
1308        Some(sum)
1309    }
1310
1311    /// Convert the Laurent series to LaTeX representation.
1312    pub fn to_latex(&self) -> String {
1313        let is_centered_at_zero = matches!(&self.center, Expression::Integer(0))
1314            || matches!(&self.center, Expression::Float(x) if x.abs() < 1e-15);
1315
1316        let var_name = &self.variable.name;
1317        let mut parts = Vec::new();
1318
1319        // Negative powers first (in descending order)
1320        let mut neg_sorted: Vec<_> = self.negative_terms.iter().collect();
1321        neg_sorted.sort_by(|a, b| b.power.cmp(&a.power));
1322
1323        for term in neg_sorted {
1324            let coeff_str = format_coefficient_latex(&term.coefficient);
1325            let var_part = if is_centered_at_zero {
1326                format!("{}^{{-{}}}", var_name, term.power)
1327            } else {
1328                format!("({} - {})^{{-{}}}", var_name, self.center, term.power)
1329            };
1330
1331            let term_str = if coeff_str == "1" {
1332                var_part
1333            } else if coeff_str == "-1" {
1334                format!("-{}", var_part)
1335            } else {
1336                format!("{} {}", coeff_str, var_part)
1337            };
1338
1339            if parts.is_empty() {
1340                parts.push(term_str);
1341            } else if term_str.starts_with('-') {
1342                parts.push(format!(" - {}", &term_str[1..]));
1343            } else {
1344                parts.push(format!(" + {}", term_str));
1345            }
1346        }
1347
1348        // Positive powers
1349        for term in &self.positive_terms {
1350            let coeff_str = format_coefficient_latex(&term.coefficient);
1351
1352            let term_str = if term.power == 0 {
1353                coeff_str
1354            } else {
1355                let var_part = if is_centered_at_zero {
1356                    if term.power == 1 {
1357                        var_name.clone()
1358                    } else {
1359                        format!("{}^{{{}}}", var_name, term.power)
1360                    }
1361                } else if term.power == 1 {
1362                    format!("({} - {})", var_name, self.center)
1363                } else {
1364                    format!("({} - {})^{{{}}}", var_name, self.center, term.power)
1365                };
1366
1367                if coeff_str == "1" {
1368                    var_part
1369                } else if coeff_str == "-1" {
1370                    format!("-{}", var_part)
1371                } else {
1372                    format!("{} {}", coeff_str, var_part)
1373                }
1374            };
1375
1376            if parts.is_empty() {
1377                parts.push(term_str);
1378            } else if term_str.starts_with('-') {
1379                parts.push(format!(" - {}", &term_str[1..]));
1380            } else {
1381                parts.push(format!(" + {}", term_str));
1382            }
1383        }
1384
1385        if parts.is_empty() {
1386            "0".to_string()
1387        } else {
1388            parts.join("")
1389        }
1390    }
1391}
1392
1393impl fmt::Display for LaurentSeries {
1394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1395        let latex = self.to_latex();
1396        // Convert LaTeX to plain text
1397        let plain = latex
1398            .replace("^{-", "^(-")
1399            .replace("^{", "^")
1400            .replace("}", ")")
1401            .replace("{", "");
1402        write!(f, "{}", plain)
1403    }
1404}
1405
1406/// Compute Laurent series expansion of an expression around a center point.
1407///
1408/// The Laurent series includes terms from (x-c)^{-neg_order} to (x-c)^{pos_order}.
1409///
1410/// # Arguments
1411/// * `expr` - The expression to expand
1412/// * `var` - The variable to expand in
1413/// * `center` - The center point of expansion
1414/// * `neg_order` - Maximum negative power (pole order)
1415/// * `pos_order` - Maximum positive power
1416///
1417/// # Example
1418/// ```rust
1419/// use thales::series::laurent;
1420/// use thales::ast::{Expression, Variable, BinaryOp};
1421///
1422/// let x = Variable::new("x");
1423/// // 1/x Laurent series around 0
1424/// let expr = Expression::Binary(
1425///     BinaryOp::Div,
1426///     Box::new(Expression::Integer(1)),
1427///     Box::new(Expression::Variable(x.clone())),
1428/// );
1429/// let series = laurent(&expr, &x, &Expression::Integer(0), 1, 3).unwrap();
1430/// // Series has term x^{-1} with coefficient 1
1431/// ```
1432pub fn laurent(
1433    expr: &Expression,
1434    var: &Variable,
1435    center: &Expression,
1436    neg_order: u32,
1437    pos_order: u32,
1438) -> SeriesResult<LaurentSeries> {
1439    let mut series = LaurentSeries::new(var.clone(), center.clone(), neg_order, pos_order);
1440
1441    // Try to detect the structure of the expression to compute Laurent coefficients
1442    match expr {
1443        // Simple case: 1/x^n centered at 0
1444        Expression::Binary(BinaryOp::Div, num, denom) => {
1445            if is_power_of_var(denom, var, center) {
1446                let power = get_var_power(denom, var);
1447                if power > 0 {
1448                    // This is num / (x-c)^power
1449                    // Laurent series of num / (x-c)^power = (Taylor of num) * (x-c)^{-power}
1450                    let num_taylor = taylor(num, var, center, pos_order + power as u32)?;
1451
1452                    for term in &num_taylor.terms {
1453                        let new_power = term.power as i32 - power;
1454                        if new_power < 0 && (-new_power as u32) <= neg_order {
1455                            series.add_negative_term(term.coefficient.clone(), (-new_power) as u32);
1456                        } else if new_power >= 0 && (new_power as u32) <= pos_order {
1457                            series.add_positive_term(SeriesTerm::new(
1458                                term.coefficient.clone(),
1459                                new_power as u32,
1460                            ));
1461                        }
1462                    }
1463
1464                    return Ok(series);
1465                }
1466            }
1467
1468            // General case: try Taylor expansion of numerator and denominator
1469            // f/g = (Taylor(f)) / (Taylor(g))
1470            // For g with a zero at center, we need to handle poles
1471            let num_taylor = taylor(num, var, center, pos_order + neg_order)?;
1472            let denom_taylor = taylor(denom, var, center, pos_order + neg_order)?;
1473
1474            // Find the order of the zero of the denominator
1475            let denom_zero_order = find_leading_power(&denom_taylor);
1476
1477            if denom_zero_order > 0 {
1478                // Denominator has a zero of order denom_zero_order
1479                // f/g has a pole of order <= denom_zero_order at center
1480
1481                // Compute f/g by polynomial long division
1482                let result = divide_series(&num_taylor, &denom_taylor, neg_order, pos_order)?;
1483
1484                for (power, coeff) in result {
1485                    if power < 0 && (-power as u32) <= neg_order {
1486                        series.add_negative_term(coeff, (-power) as u32);
1487                    } else if power >= 0 && (power as u32) <= pos_order {
1488                        series.add_positive_term(SeriesTerm::new(coeff, power as u32));
1489                    }
1490                }
1491            } else {
1492                // Denominator doesn't vanish at center, just use Taylor series
1493                let regular_taylor = taylor(expr, var, center, pos_order)?;
1494                for term in regular_taylor.terms {
1495                    series.add_positive_term(term);
1496                }
1497            }
1498        }
1499
1500        Expression::Power(base, exp) => {
1501            // Handle (x-c)^{-n} type expressions
1502            if let Expression::Unary(crate::ast::UnaryOp::Neg, inner_exp) = exp.as_ref() {
1503                if is_var_minus_center(base, var, center) {
1504                    if let Some(n) = extract_positive_integer(inner_exp) {
1505                        // This is (x-c)^{-n}
1506                        if n <= neg_order {
1507                            series.add_negative_term(Expression::Integer(1), n);
1508                        }
1509                        return Ok(series);
1510                    }
1511                }
1512            }
1513
1514            // Fall back to Taylor series for other power expressions
1515            let regular_taylor = taylor(expr, var, center, pos_order)?;
1516            for term in regular_taylor.terms {
1517                series.add_positive_term(term);
1518            }
1519        }
1520
1521        _ => {
1522            // For expressions without obvious poles, compute Taylor series
1523            let regular_taylor = taylor(expr, var, center, pos_order)?;
1524            for term in regular_taylor.terms {
1525                series.add_positive_term(term);
1526            }
1527        }
1528    }
1529
1530    Ok(series)
1531}
1532
1533/// Compute the residue of an expression at a pole.
1534///
1535/// The residue is the coefficient of (x - center)^{-1} in the Laurent series.
1536///
1537/// # Example
1538/// ```rust
1539/// use thales::series::residue;
1540/// use thales::ast::{Expression, Variable, BinaryOp};
1541///
1542/// let x = Variable::new("x");
1543/// // Residue of 1/(x-1) at x=1 is 1
1544/// let expr = Expression::Binary(
1545///     BinaryOp::Div,
1546///     Box::new(Expression::Integer(1)),
1547///     Box::new(Expression::Binary(
1548///         BinaryOp::Sub,
1549///         Box::new(Expression::Variable(x.clone())),
1550///         Box::new(Expression::Integer(1)),
1551///     )),
1552/// );
1553/// let res = residue(&expr, &x, &Expression::Integer(1)).unwrap();
1554/// ```
1555pub fn residue(expr: &Expression, var: &Variable, pole: &Expression) -> SeriesResult<Expression> {
1556    // Compute Laurent series around the pole with enough negative terms
1557    let laurent_series = laurent(expr, var, pole, 5, 0)?;
1558    Ok(laurent_series.residue())
1559}
1560
1561/// Find the order of a pole at a given point.
1562///
1563/// Returns 0 if the point is not a pole (regular point or removable singularity).
1564pub fn pole_order(expr: &Expression, var: &Variable, point: &Expression) -> SeriesResult<u32> {
1565    let laurent_series = laurent(expr, var, point, 10, 0)?;
1566
1567    // Find the highest negative power with non-zero coefficient
1568    let mut max_order = 0;
1569    for term in &laurent_series.negative_terms {
1570        if term.power > max_order {
1571            max_order = term.power;
1572        }
1573    }
1574
1575    Ok(max_order)
1576}
1577
1578/// Find singularities of an expression.
1579///
1580/// Currently handles rational functions by finding zeros of the denominator.
1581pub fn find_singularities(expr: &Expression, var: &Variable) -> Vec<Singularity> {
1582    let mut singularities = Vec::new();
1583
1584    match expr {
1585        Expression::Binary(BinaryOp::Div, _num, denom) => {
1586            // Find zeros of denominator
1587            let zeros = find_zeros_of_expression(denom, var);
1588
1589            for zero in zeros {
1590                // Determine singularity type
1591                let order = get_zero_multiplicity(denom, var, &zero);
1592                let sing_type = if order > 0 {
1593                    SingularityType::Pole(order)
1594                } else {
1595                    SingularityType::Removable
1596                };
1597
1598                singularities.push(Singularity::new(zero, sing_type));
1599            }
1600        }
1601        _ => {
1602            // For non-rational expressions, singularity detection is more complex
1603            // This is a simplified implementation
1604        }
1605    }
1606
1607    singularities
1608}
1609
1610// Helper functions for Laurent series
1611
1612fn is_power_of_var(expr: &Expression, var: &Variable, center: &Expression) -> bool {
1613    match expr {
1614        Expression::Variable(v) if v.name == var.name => {
1615            matches!(center, Expression::Integer(0))
1616        }
1617        Expression::Power(base, _) => is_var_minus_center(base, var, center),
1618        Expression::Binary(BinaryOp::Sub, left, right) => {
1619            matches!(left.as_ref(), Expression::Variable(v) if v.name == var.name)
1620                && expressions_equal(right, center)
1621        }
1622        _ => false,
1623    }
1624}
1625
1626fn is_var_minus_center(expr: &Expression, var: &Variable, center: &Expression) -> bool {
1627    match expr {
1628        Expression::Variable(v) if v.name == var.name => {
1629            matches!(center, Expression::Integer(0))
1630        }
1631        Expression::Binary(BinaryOp::Sub, left, right) => {
1632            matches!(left.as_ref(), Expression::Variable(v) if v.name == var.name)
1633                && expressions_equal(right, center)
1634        }
1635        _ => false,
1636    }
1637}
1638
1639fn get_var_power(expr: &Expression, var: &Variable) -> i32 {
1640    match expr {
1641        Expression::Variable(v) if v.name == var.name => 1,
1642        Expression::Power(base, exp) => {
1643            if let Expression::Variable(v) = base.as_ref() {
1644                if v.name == var.name {
1645                    if let Some(n) = extract_integer(exp) {
1646                        return n as i32;
1647                    }
1648                }
1649            }
1650            1
1651        }
1652        Expression::Binary(BinaryOp::Sub, left, _) => {
1653            if let Expression::Variable(v) = left.as_ref() {
1654                if v.name == var.name {
1655                    return 1;
1656                }
1657            }
1658            0
1659        }
1660        _ => 0,
1661    }
1662}
1663
1664fn extract_integer(expr: &Expression) -> Option<i64> {
1665    match expr {
1666        Expression::Integer(n) => Some(*n),
1667        Expression::Float(f) if f.fract() == 0.0 => Some(*f as i64),
1668        _ => None,
1669    }
1670}
1671
1672fn extract_positive_integer(expr: &Expression) -> Option<u32> {
1673    extract_integer(expr).and_then(|n| if n > 0 { Some(n as u32) } else { None })
1674}
1675
1676fn expressions_equal(a: &Expression, b: &Expression) -> bool {
1677    // Simple equality check - could be improved with simplification
1678    match (a, b) {
1679        (Expression::Integer(x), Expression::Integer(y)) => x == y,
1680        (Expression::Float(x), Expression::Float(y)) => (x - y).abs() < 1e-15,
1681        (Expression::Integer(x), Expression::Float(y))
1682        | (Expression::Float(y), Expression::Integer(x)) => (*x as f64 - y).abs() < 1e-15,
1683        (Expression::Variable(v1), Expression::Variable(v2)) => v1.name == v2.name,
1684        _ => false,
1685    }
1686}
1687
1688fn find_leading_power(series: &Series) -> u32 {
1689    series
1690        .terms
1691        .iter()
1692        .filter(|t| !t.is_zero())
1693        .map(|t| t.power)
1694        .min()
1695        .unwrap_or(0)
1696}
1697
1698fn divide_series(
1699    num: &Series,
1700    denom: &Series,
1701    neg_order: u32,
1702    pos_order: u32,
1703) -> SeriesResult<Vec<(i32, Expression)>> {
1704    // Polynomial long division for series
1705    // This is a simplified implementation
1706
1707    let mut result = Vec::new();
1708    let denom_lead_power = find_leading_power(denom) as i32;
1709    let denom_lead_coeff = denom
1710        .terms
1711        .iter()
1712        .find(|t| t.power as i32 == denom_lead_power)
1713        .map(|t| &t.coefficient);
1714
1715    if denom_lead_coeff.is_none() {
1716        return Err(SeriesError::DivisionByZero);
1717    }
1718
1719    // Simple case: if denominator leading term is just a constant multiple of x^n
1720    // We can compute the quotient directly
1721
1722    for num_term in &num.terms {
1723        let result_power = num_term.power as i32 - denom_lead_power;
1724
1725        if result_power >= -(neg_order as i32) && result_power <= pos_order as i32 {
1726            // Divide coefficient by leading denominator coefficient
1727            let coeff = match denom_lead_coeff {
1728                Some(Expression::Integer(d)) if *d != 0 => match &num_term.coefficient {
1729                    Expression::Integer(n) => {
1730                        Expression::Rational(num_rational::Rational64::new(*n, *d))
1731                    }
1732                    Expression::Rational(r) => {
1733                        Expression::Rational(*r / num_rational::Rational64::from(*d))
1734                    }
1735                    other => Expression::Binary(
1736                        BinaryOp::Div,
1737                        Box::new(other.clone()),
1738                        Box::new(Expression::Integer(*d)),
1739                    ),
1740                },
1741                _ => num_term.coefficient.clone(),
1742            };
1743
1744            result.push((result_power, coeff));
1745        }
1746    }
1747
1748    Ok(result)
1749}
1750
1751fn find_zeros_of_expression(expr: &Expression, var: &Variable) -> Vec<Expression> {
1752    // Simplified zero finding - only handles simple cases
1753    let mut zeros = Vec::new();
1754
1755    match expr {
1756        Expression::Variable(v) if v.name == var.name => {
1757            zeros.push(Expression::Integer(0));
1758        }
1759        Expression::Binary(BinaryOp::Sub, left, right) => {
1760            if matches!(left.as_ref(), Expression::Variable(v) if v.name == var.name) {
1761                zeros.push(right.as_ref().clone());
1762            }
1763        }
1764        Expression::Binary(BinaryOp::Mul, left, right) => {
1765            zeros.extend(find_zeros_of_expression(left, var));
1766            zeros.extend(find_zeros_of_expression(right, var));
1767        }
1768        Expression::Power(base, _) => {
1769            zeros.extend(find_zeros_of_expression(base, var));
1770        }
1771        _ => {}
1772    }
1773
1774    zeros
1775}
1776
1777fn get_zero_multiplicity(expr: &Expression, var: &Variable, zero: &Expression) -> u32 {
1778    match expr {
1779        Expression::Power(base, exp) => {
1780            let base_zeros = find_zeros_of_expression(base, var);
1781            if base_zeros.iter().any(|z| expressions_equal(z, zero)) {
1782                extract_integer(exp).unwrap_or(1) as u32
1783            } else {
1784                0
1785            }
1786        }
1787        Expression::Binary(BinaryOp::Mul, left, right) => {
1788            get_zero_multiplicity(left, var, zero) + get_zero_multiplicity(right, var, zero)
1789        }
1790        _ => {
1791            let zeros = find_zeros_of_expression(expr, var);
1792            if zeros.iter().any(|z| expressions_equal(z, zero)) {
1793                1
1794            } else {
1795                0
1796            }
1797        }
1798    }
1799}
1800
1801/// Direction for asymptotic expansion.
1802#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1803pub enum AsymptoticDirection {
1804    /// Expansion as x approaches positive infinity.
1805    PosInfinity,
1806    /// Expansion as x approaches negative infinity.
1807    NegInfinity,
1808    /// Expansion as x approaches zero.
1809    Zero,
1810}
1811
1812impl fmt::Display for AsymptoticDirection {
1813    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1814        match self {
1815            AsymptoticDirection::PosInfinity => write!(f, "x→+∞"),
1816            AsymptoticDirection::NegInfinity => write!(f, "x→-∞"),
1817            AsymptoticDirection::Zero => write!(f, "x→0"),
1818        }
1819    }
1820}
1821
1822/// A single term in an asymptotic series: coefficient × x^exponent.
1823/// The exponent can be negative or fractional for asymptotic expansions.
1824#[derive(Debug, Clone, PartialEq)]
1825pub struct AsymptoticTerm {
1826    /// The coefficient of this term (can be symbolic).
1827    pub coefficient: Expression,
1828    /// The exponent (can be negative, fractional, or symbolic).
1829    pub exponent: Expression,
1830}
1831
1832impl AsymptoticTerm {
1833    /// Create a new asymptotic term.
1834    pub fn new(coefficient: Expression, exponent: Expression) -> Self {
1835        AsymptoticTerm {
1836            coefficient,
1837            exponent,
1838        }
1839    }
1840
1841    /// Check if this term has a zero coefficient.
1842    pub fn is_zero(&self) -> bool {
1843        matches!(&self.coefficient, Expression::Integer(0))
1844            || matches!(&self.coefficient, Expression::Float(x) if x.abs() < 1e-15)
1845    }
1846
1847    /// Try to evaluate this term at a given point.
1848    pub fn evaluate(&self, var: &Variable, point: f64) -> Option<f64> {
1849        // Substitute the variable with the point value
1850        let point_expr = Expression::Float(point);
1851
1852        let coeff_result = evaluate_at(&self.coefficient, var, &point_expr).ok()?;
1853        let exp_result = evaluate_at(&self.exponent, var, &point_expr).ok()?;
1854
1855        let coeff = try_expr_to_f64(&coeff_result)?;
1856        let exp = try_expr_to_f64(&exp_result)?;
1857
1858        Some(coeff * point.powf(exp))
1859    }
1860}
1861
1862impl fmt::Display for AsymptoticTerm {
1863    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1864        // Format based on exponent value
1865        match &self.exponent {
1866            Expression::Integer(0) => write!(f, "{}", self.coefficient),
1867            Expression::Integer(1) => write!(f, "{}·x", self.coefficient),
1868            Expression::Integer(n) if *n < 0 => {
1869                write!(f, "{}/x^{}", self.coefficient, -n)
1870            }
1871            Expression::Rational(r) => {
1872                write!(f, "{}·x^({})", self.coefficient, r)
1873            }
1874            _ => write!(f, "{}·x^({})", self.coefficient, self.exponent),
1875        }
1876    }
1877}
1878
1879/// Big-O notation for asymptotic order.
1880#[derive(Debug, Clone, PartialEq)]
1881pub struct BigO {
1882    /// Order expression (e.g., x^n, log(x), etc.)
1883    pub order: Expression,
1884    /// Variable of the expansion.
1885    pub variable: Variable,
1886}
1887
1888impl BigO {
1889    /// Create a new Big-O term.
1890    pub fn new(order: Expression, variable: Variable) -> Self {
1891        BigO { order, variable }
1892    }
1893
1894    /// Check if two Big-O terms have the same order.
1895    pub fn is_same_order(&self, other: &BigO) -> bool {
1896        // Simplified check - would need more sophisticated comparison
1897        self.order == other.order
1898    }
1899
1900    /// Check if this Big-O is smaller than another.
1901    /// Returns true if this order grows slower than other.
1902    pub fn is_smaller_order(&self, other: &BigO) -> bool {
1903        // For power terms: O(x^a) < O(x^b) if a < b
1904        match (&self.order, &other.order) {
1905            (Expression::Power(_, exp1), Expression::Power(_, exp2)) => {
1906                if let (Some(e1), Some(e2)) = (try_expr_to_f64(exp1), try_expr_to_f64(exp2)) {
1907                    return e1 < e2;
1908                }
1909            }
1910            (Expression::Integer(a), Expression::Power(_, _)) => {
1911                // Constant is smaller than any power
1912                return *a == 1;
1913            }
1914            _ => {}
1915        }
1916        false
1917    }
1918
1919    /// Check if an expression is bounded by this Big-O term.
1920    pub fn is_bounded_by(&self, expr: &Expression) -> bool {
1921        // Simplified check - full implementation would need limit analysis
1922        expr == &self.order
1923    }
1924}
1925
1926impl fmt::Display for BigO {
1927    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1928        write!(f, "O({})", self.order)
1929    }
1930}
1931
1932/// A complete asymptotic series representation.
1933#[derive(Debug, Clone, PartialEq)]
1934pub struct AsymptoticSeries {
1935    /// The terms of the series, ordered by decreasing dominance.
1936    pub terms: Vec<AsymptoticTerm>,
1937    /// The variable of expansion.
1938    pub variable: Variable,
1939    /// Direction of the asymptotic expansion.
1940    pub direction: AsymptoticDirection,
1941}
1942
1943impl AsymptoticSeries {
1944    /// Create a new empty asymptotic series.
1945    pub fn new(variable: Variable, direction: AsymptoticDirection) -> Self {
1946        AsymptoticSeries {
1947            terms: Vec::new(),
1948            variable,
1949            direction,
1950        }
1951    }
1952
1953    /// Add a term to the series.
1954    pub fn add_term(&mut self, term: AsymptoticTerm) {
1955        if !term.is_zero() {
1956            self.terms.push(term);
1957        }
1958    }
1959
1960    /// Get the dominant (leading) term.
1961    pub fn dominant_term(&self) -> Option<&AsymptoticTerm> {
1962        self.terms.first()
1963    }
1964
1965    /// Get the order of magnitude (exponent of dominant term).
1966    pub fn order_of_magnitude(&self) -> Option<Expression> {
1967        self.dominant_term().map(|t| t.exponent.clone())
1968    }
1969
1970    /// Return the series with its error term.
1971    pub fn with_error_term(&self) -> (Self, BigO) {
1972        let series = self.clone();
1973
1974        // Error term is the next order after the smallest term
1975        let error_order = if let Some(last_term) = self.terms.last() {
1976            // For x->inf: if last term is x^n, error is O(x^(n-1))
1977            // For x->0: if last term is x^n, error is O(x^(n+1))
1978            match self.direction {
1979                AsymptoticDirection::PosInfinity | AsymptoticDirection::NegInfinity => {
1980                    Expression::Binary(
1981                        BinaryOp::Sub,
1982                        Box::new(last_term.exponent.clone()),
1983                        Box::new(Expression::Integer(1)),
1984                    )
1985                }
1986                AsymptoticDirection::Zero => Expression::Binary(
1987                    BinaryOp::Add,
1988                    Box::new(last_term.exponent.clone()),
1989                    Box::new(Expression::Integer(1)),
1990                ),
1991            }
1992        } else {
1993            Expression::Integer(0)
1994        };
1995
1996        let var_expr = Expression::Variable(self.variable.clone());
1997        let big_o = BigO::new(
1998            Expression::Power(Box::new(var_expr), Box::new(error_order)),
1999            self.variable.clone(),
2000        );
2001
2002        (series, big_o)
2003    }
2004
2005    /// Evaluate the series at a given point.
2006    pub fn evaluate(&self, point: f64) -> Option<f64> {
2007        let mut sum = 0.0;
2008        for term in &self.terms {
2009            sum += term.evaluate(&self.variable, point)?;
2010        }
2011        Some(sum)
2012    }
2013
2014    /// Convert to a symbolic Expression.
2015    pub fn to_expression(&self) -> Expression {
2016        if self.terms.is_empty() {
2017            return Expression::Integer(0);
2018        }
2019
2020        let var_expr = Expression::Variable(self.variable.clone());
2021        let mut result: Option<Expression> = None;
2022
2023        for term in &self.terms {
2024            let term_expr = Expression::Binary(
2025                BinaryOp::Mul,
2026                Box::new(term.coefficient.clone()),
2027                Box::new(Expression::Power(
2028                    Box::new(var_expr.clone()),
2029                    Box::new(term.exponent.clone()),
2030                )),
2031            );
2032
2033            result = Some(match result {
2034                None => term_expr,
2035                Some(acc) => Expression::Binary(BinaryOp::Add, Box::new(acc), Box::new(term_expr)),
2036            });
2037        }
2038
2039        result.unwrap_or(Expression::Integer(0)).simplify()
2040    }
2041}
2042
2043impl fmt::Display for AsymptoticSeries {
2044    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2045        if self.terms.is_empty() {
2046            return write!(f, "0");
2047        }
2048
2049        write!(f, "As {}: ", self.direction)?;
2050
2051        for (i, term) in self.terms.iter().enumerate() {
2052            if i > 0 {
2053                write!(f, " + ")?;
2054            }
2055            write!(f, "{}", term)?;
2056        }
2057
2058        Ok(())
2059    }
2060}
2061
2062/// Compute asymptotic expansion of an expression.
2063///
2064/// This function computes the asymptotic expansion of an expression as the variable
2065/// approaches a limit (zero or infinity). The expansion includes terms in decreasing
2066/// order of dominance.
2067///
2068/// # Arguments
2069///
2070/// * `expr` - The expression to expand
2071/// * `var` - The variable for expansion
2072/// * `direction` - Direction of approach (zero or infinity)
2073/// * `num_terms` - Number of terms to compute
2074///
2075/// # Returns
2076///
2077/// An `AsymptoticSeries` containing the dominant terms.
2078///
2079/// # Examples
2080///
2081/// ```
2082/// use thales::series::{asymptotic, AsymptoticDirection};
2083/// use thales::parser::parse_expression;
2084///
2085/// // Asymptotic expansion of 1/x + 1/x^2 as x→∞
2086/// let expr = parse_expression("1/x + 1/x^2").unwrap();
2087/// let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
2088/// // Returns: 1/x + 1/x^2
2089/// ```
2090pub fn asymptotic(
2091    expr: &Expression,
2092    var: impl AsRef<str>,
2093    direction: AsymptoticDirection,
2094    num_terms: u32,
2095) -> SeriesResult<AsymptoticSeries> {
2096    let var_name = var.as_ref();
2097    let variable = Variable::new(var_name);
2098
2099    let mut series = AsymptoticSeries::new(variable.clone(), direction);
2100
2101    // Extract terms directly from the expression
2102    // No substitution needed - we handle dominance in sorting
2103    extract_asymptotic_terms(expr, &variable, &mut series, num_terms)?;
2104
2105    // Sort terms by dominance
2106    sort_by_dominance(&mut series.terms, direction);
2107
2108    Ok(series)
2109}
2110
2111/// Substitute x = 1/t for infinity analysis.
2112fn substitute_for_infinity(expr: &Expression, var: &Variable) -> Expression {
2113    match expr {
2114        Expression::Variable(v) if v == var => {
2115            // x -> 1/t
2116            let t = Variable::new("__t");
2117            Expression::Binary(
2118                BinaryOp::Div,
2119                Box::new(Expression::Integer(1)),
2120                Box::new(Expression::Variable(t)),
2121            )
2122        }
2123        Expression::Binary(op, left, right) => Expression::Binary(
2124            *op,
2125            Box::new(substitute_for_infinity(left, var)),
2126            Box::new(substitute_for_infinity(right, var)),
2127        ),
2128        Expression::Unary(op, inner) => {
2129            Expression::Unary(*op, Box::new(substitute_for_infinity(inner, var)))
2130        }
2131        Expression::Power(base, exp) => Expression::Power(
2132            Box::new(substitute_for_infinity(base, var)),
2133            Box::new(substitute_for_infinity(exp, var)),
2134        ),
2135        Expression::Function(f, args) => Expression::Function(
2136            f.clone(),
2137            args.iter()
2138                .map(|a| substitute_for_infinity(a, var))
2139                .collect(),
2140        ),
2141        _ => expr.clone(),
2142    }
2143}
2144
2145/// Extract asymptotic terms from an expression.
2146fn extract_asymptotic_terms(
2147    expr: &Expression,
2148    var: &Variable,
2149    series: &mut AsymptoticSeries,
2150    num_terms: u32,
2151) -> SeriesResult<()> {
2152    // Simple extraction for basic forms
2153    match expr {
2154        Expression::Binary(BinaryOp::Add, left, right) => {
2155            extract_asymptotic_terms(left, var, series, num_terms)?;
2156            extract_asymptotic_terms(right, var, series, num_terms)?;
2157        }
2158        Expression::Binary(BinaryOp::Div, num, den) => {
2159            // Check if numerator is effectively 1 (could be Integer(1) or Float(1.0))
2160            let is_one = matches!(num.as_ref(), Expression::Integer(1))
2161                || matches!(num.as_ref(), Expression::Float(f) if (*f - 1.0).abs() < 1e-15);
2162
2163            if is_one {
2164                // Handle 1/x
2165                if let Expression::Variable(v) = den.as_ref() {
2166                    if v == var {
2167                        series.add_term(AsymptoticTerm::new(
2168                            Expression::Integer(1),
2169                            Expression::Integer(-1),
2170                        ));
2171                    }
2172                // Handle 1/x^n
2173                } else if let Expression::Power(base, exp) = den.as_ref() {
2174                    if let Expression::Variable(v) = base.as_ref() {
2175                        if v == var {
2176                            // 1/x^n -> coefficient=1, exponent=-n
2177                            let neg_exp = Expression::Unary(
2178                                crate::ast::UnaryOp::Neg,
2179                                Box::new(exp.as_ref().clone()),
2180                            )
2181                            .simplify();
2182                            series.add_term(AsymptoticTerm::new(Expression::Integer(1), neg_exp));
2183                        }
2184                    }
2185                }
2186            } else {
2187                // Handle general a/x^n form
2188                if let Expression::Variable(v) = den.as_ref() {
2189                    if v == var {
2190                        series.add_term(AsymptoticTerm::new(
2191                            num.as_ref().clone(),
2192                            Expression::Integer(-1),
2193                        ));
2194                    }
2195                } else if let Expression::Power(base, exp) = den.as_ref() {
2196                    if let Expression::Variable(v) = base.as_ref() {
2197                        if v == var {
2198                            let neg_exp = Expression::Unary(
2199                                crate::ast::UnaryOp::Neg,
2200                                Box::new(exp.as_ref().clone()),
2201                            )
2202                            .simplify();
2203                            series.add_term(AsymptoticTerm::new(num.as_ref().clone(), neg_exp));
2204                        }
2205                    }
2206                }
2207            }
2208        }
2209        Expression::Power(base, exp) => {
2210            if let Expression::Variable(v) = base.as_ref() {
2211                if v == var {
2212                    series.add_term(AsymptoticTerm::new(
2213                        Expression::Integer(1),
2214                        exp.as_ref().clone(),
2215                    ));
2216                }
2217            }
2218        }
2219        Expression::Variable(v) if v == var => {
2220            series.add_term(AsymptoticTerm::new(
2221                Expression::Integer(1),
2222                Expression::Integer(1),
2223            ));
2224        }
2225        Expression::Integer(n) => {
2226            series.add_term(AsymptoticTerm::new(
2227                Expression::Integer(*n),
2228                Expression::Integer(0),
2229            ));
2230        }
2231        Expression::Float(f) => {
2232            series.add_term(AsymptoticTerm::new(
2233                Expression::Float(*f),
2234                Expression::Integer(0),
2235            ));
2236        }
2237        _ => {
2238            return Err(SeriesError::CannotExpand(format!(
2239                "Cannot extract asymptotic terms from: {}",
2240                expr
2241            )));
2242        }
2243    }
2244
2245    Ok(())
2246}
2247
2248/// Sort terms by dominance for the given direction.
2249fn sort_by_dominance(terms: &mut Vec<AsymptoticTerm>, direction: AsymptoticDirection) {
2250    terms.sort_by(|a, b| {
2251        let exp_a = try_expr_to_f64(&a.exponent).unwrap_or(0.0);
2252        let exp_b = try_expr_to_f64(&b.exponent).unwrap_or(0.0);
2253
2254        match direction {
2255            AsymptoticDirection::PosInfinity | AsymptoticDirection::NegInfinity => {
2256                // For x→∞, larger exponents dominate
2257                exp_b.partial_cmp(&exp_a).unwrap()
2258            }
2259            AsymptoticDirection::Zero => {
2260                // For x→0, smaller exponents dominate
2261                exp_a.partial_cmp(&exp_b).unwrap()
2262            }
2263        }
2264    });
2265}
2266
2267/// Compute limit via asymptotic expansion.
2268///
2269/// This function uses asymptotic expansion to compute limits, especially
2270/// useful for limits at infinity where standard Taylor series don't apply.
2271///
2272/// # Arguments
2273///
2274/// * `expr` - The expression to compute the limit of
2275/// * `var` - The variable approaching the limit
2276/// * `direction` - Direction of approach
2277///
2278/// # Returns
2279///
2280/// The limit value as a `LimitResult`.
2281pub fn limit_via_asymptotic(
2282    expr: &Expression,
2283    var: impl AsRef<str>,
2284    direction: AsymptoticDirection,
2285) -> Result<crate::limits::LimitResult, crate::limits::LimitError> {
2286    use crate::limits::LimitResult;
2287
2288    let series = asymptotic(expr, var.as_ref(), direction, 5)
2289        .map_err(|e| crate::limits::LimitError::EvaluationError(e.to_string()))?;
2290
2291    if let Some(dominant) = series.dominant_term() {
2292        // Check the exponent of the dominant term
2293        if let Some(exp) = try_expr_to_f64(&dominant.exponent) {
2294            match direction {
2295                AsymptoticDirection::PosInfinity | AsymptoticDirection::NegInfinity => {
2296                    if exp > 0.0 {
2297                        // Dominant term has positive exponent -> infinity
2298                        if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
2299                            if coeff > 0.0 {
2300                                return Ok(LimitResult::PositiveInfinity);
2301                            } else if coeff < 0.0 {
2302                                return Ok(LimitResult::NegativeInfinity);
2303                            }
2304                        }
2305                    } else if exp < 0.0 {
2306                        // Dominant term has negative exponent -> 0
2307                        return Ok(LimitResult::Value(0.0));
2308                    } else {
2309                        // Constant term
2310                        if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
2311                            return Ok(LimitResult::Value(coeff));
2312                        }
2313                    }
2314                }
2315                AsymptoticDirection::Zero => {
2316                    if exp > 0.0 {
2317                        // As x→0, x^n → 0 for n > 0
2318                        return Ok(LimitResult::Value(0.0));
2319                    } else if exp < 0.0 {
2320                        // As x→0, x^(-n) → ∞ for n > 0
2321                        if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
2322                            if coeff > 0.0 {
2323                                return Ok(LimitResult::PositiveInfinity);
2324                            } else if coeff < 0.0 {
2325                                return Ok(LimitResult::NegativeInfinity);
2326                            }
2327                        }
2328                    } else {
2329                        // Constant term
2330                        if let Some(coeff) = try_expr_to_f64(&dominant.coefficient) {
2331                            return Ok(LimitResult::Value(coeff));
2332                        }
2333                    }
2334                }
2335            }
2336        }
2337    }
2338
2339    Err(crate::limits::LimitError::EvaluationError(
2340        "Cannot determine limit from asymptotic expansion".to_string(),
2341    ))
2342}
2343
2344#[cfg(test)]
2345mod tests {
2346    use super::*;
2347
2348    #[test]
2349    fn test_factorial() {
2350        assert_eq!(factorial(0), 1);
2351        assert_eq!(factorial(1), 1);
2352        assert_eq!(factorial(5), 120);
2353        assert_eq!(factorial(10), 3628800);
2354    }
2355
2356    #[test]
2357    fn test_exp_series() {
2358        let x = Variable::new("x");
2359        let series = exp_series(&x, 4);
2360
2361        assert_eq!(series.term_count(), 5);
2362        assert_eq!(series.order, 4);
2363
2364        // Check coefficients: 1, 1, 1/2, 1/6, 1/24
2365        let term0 = series.get_term(0).unwrap();
2366        assert!(matches!(&term0.coefficient, Expression::Integer(1)));
2367
2368        let term2 = series.get_term(2).unwrap();
2369        if let Expression::Rational(r) = &term2.coefficient {
2370            assert_eq!(*r.numer(), 1);
2371            assert_eq!(*r.denom(), 2);
2372        } else {
2373            panic!("Expected rational coefficient");
2374        }
2375    }
2376
2377    #[test]
2378    fn test_sin_series() {
2379        let x = Variable::new("x");
2380        let series = sin_series(&x, 7);
2381
2382        // Should have terms at powers 1, 3, 5, 7
2383        assert!(series.get_term(0).is_none());
2384        assert!(series.get_term(1).is_some());
2385        assert!(series.get_term(2).is_none());
2386        assert!(series.get_term(3).is_some());
2387
2388        // First term should be x (coefficient 1)
2389        let term1 = series.get_term(1).unwrap();
2390        if let Expression::Rational(r) = &term1.coefficient {
2391            assert_eq!(*r.numer(), 1);
2392            assert_eq!(*r.denom(), 1);
2393        }
2394
2395        // x^3 coefficient should be -1/6
2396        let term3 = series.get_term(3).unwrap();
2397        if let Expression::Rational(r) = &term3.coefficient {
2398            assert_eq!(*r.numer(), -1);
2399            assert_eq!(*r.denom(), 6);
2400        }
2401    }
2402
2403    #[test]
2404    fn test_cos_series() {
2405        let x = Variable::new("x");
2406        let series = cos_series(&x, 6);
2407
2408        // Should have terms at powers 0, 2, 4, 6
2409        assert!(series.get_term(0).is_some());
2410        assert!(series.get_term(1).is_none());
2411        assert!(series.get_term(2).is_some());
2412
2413        // First term should be 1
2414        let term0 = series.get_term(0).unwrap();
2415        if let Expression::Rational(r) = &term0.coefficient {
2416            assert_eq!(*r.numer(), 1);
2417        }
2418
2419        // x^2 coefficient should be -1/2
2420        let term2 = series.get_term(2).unwrap();
2421        if let Expression::Rational(r) = &term2.coefficient {
2422            assert_eq!(*r.numer(), -1);
2423            assert_eq!(*r.denom(), 2);
2424        }
2425    }
2426
2427    #[test]
2428    fn test_series_evaluate() {
2429        let x = Variable::new("x");
2430        let series = exp_series(&x, 10);
2431
2432        // e^0.1 ≈ 1.10517...
2433        let result = series.evaluate(0.1).unwrap();
2434        let expected = 0.1_f64.exp();
2435        assert!((result - expected).abs() < 1e-8);
2436    }
2437
2438    #[test]
2439    fn test_sin_series_evaluate() {
2440        let x = Variable::new("x");
2441        let series = sin_series(&x, 11);
2442
2443        // sin(0.5) ≈ 0.4794...
2444        let result = series.evaluate(0.5).unwrap();
2445        let expected = 0.5_f64.sin();
2446        assert!((result - expected).abs() < 1e-8);
2447    }
2448
2449    #[test]
2450    fn test_series_to_expression() {
2451        let x = Variable::new("x");
2452        let series = exp_series(&x, 3);
2453        let expr = series.to_expression();
2454
2455        // Should be able to evaluate the expression
2456        let mut vars = HashMap::new();
2457        vars.insert("x".to_string(), 0.1);
2458        let result = expr.evaluate(&vars).unwrap();
2459        let expected = 1.0 + 0.1 + 0.01 / 2.0 + 0.001 / 6.0;
2460        assert!((result - expected).abs() < 1e-10);
2461    }
2462
2463    #[test]
2464    fn test_series_to_latex() {
2465        let x = Variable::new("x");
2466        let series = sin_series(&x, 5);
2467        let latex = series.to_latex();
2468
2469        // Should contain x and x^3 terms
2470        assert!(latex.contains("x"));
2471        assert!(latex.contains("x^{3}") || latex.contains("x³"));
2472    }
2473
2474    #[test]
2475    fn test_ln_1_plus_x_series() {
2476        let x = Variable::new("x");
2477        let series = ln_1_plus_x_series(&x, 5);
2478
2479        // Should have terms 1, 2, 3, 4, 5 (no constant term)
2480        assert!(series.get_term(0).is_none());
2481        assert!(series.get_term(1).is_some());
2482
2483        // x coefficient should be 1
2484        let term1 = series.get_term(1).unwrap();
2485        if let Expression::Rational(r) = &term1.coefficient {
2486            assert_eq!(*r.numer(), 1);
2487            assert_eq!(*r.denom(), 1);
2488        }
2489
2490        // x^2 coefficient should be -1/2
2491        let term2 = series.get_term(2).unwrap();
2492        if let Expression::Rational(r) = &term2.coefficient {
2493            assert_eq!(*r.numer(), -1);
2494            assert_eq!(*r.denom(), 2);
2495        }
2496    }
2497
2498    #[test]
2499    fn test_arctan_series() {
2500        let x = Variable::new("x");
2501        let series = arctan_series(&x, 11);
2502
2503        // arctan(0.3) ≈ 0.2915...
2504        let result = series.evaluate(0.3).unwrap();
2505        let expected = 0.3_f64.atan();
2506        assert!((result - expected).abs() < 1e-4);
2507    }
2508
2509    #[test]
2510    fn test_binomial_series() {
2511        let x = Variable::new("x");
2512
2513        // (1+x)^2 should give 1 + 2x + x^2 exactly
2514        let series = binomial_series(&Expression::Integer(2), &x, 5).unwrap();
2515        assert_eq!(series.term_count(), 3);
2516
2517        // (1+x)^0.5 should give sqrt(1+x) approximation
2518        let series = binomial_series(&Expression::Float(0.5), &x, 5).unwrap();
2519        let result = series.evaluate(0.21).unwrap(); // sqrt(1.21) = 1.1
2520        assert!((result - 1.1).abs() < 0.01);
2521    }
2522
2523    #[test]
2524    fn test_taylor_polynomial() {
2525        let x = Variable::new("x");
2526        // x^2 Taylor series around 0 should just be x^2
2527        let expr = Expression::Power(
2528            Box::new(Expression::Variable(x.clone())),
2529            Box::new(Expression::Integer(2)),
2530        );
2531
2532        let series = taylor(&expr, &x, &Expression::Integer(0), 3).unwrap();
2533
2534        // Should have just the x^2 term
2535        let term2 = series.get_term(2);
2536        assert!(term2.is_some());
2537    }
2538
2539    #[test]
2540    fn test_maclaurin_exp() {
2541        let x = Variable::new("x");
2542        let expr = Expression::Function(Function::Exp, vec![Expression::Variable(x.clone())]);
2543
2544        let series = maclaurin(&expr, &x, 5).unwrap();
2545
2546        // Should match exp_series
2547        let expected = exp_series(&x, 5);
2548        assert_eq!(series.term_count(), expected.term_count());
2549    }
2550
2551    // Laurent Series Tests
2552
2553    #[test]
2554    fn test_singularity_type_display() {
2555        let pole = SingularityType::Pole(2);
2556        assert_eq!(format!("{}", pole), "pole of order 2");
2557
2558        let removable = SingularityType::Removable;
2559        assert_eq!(format!("{}", removable), "removable singularity");
2560
2561        let essential = SingularityType::Essential;
2562        assert_eq!(format!("{}", essential), "essential singularity");
2563    }
2564
2565    #[test]
2566    fn test_laurent_series_creation() {
2567        let z = Variable::new("z");
2568        let center = Expression::Integer(0);
2569
2570        // Create a simple Laurent series with positive and negative terms
2571        let mut laurent = LaurentSeries::new(z.clone(), center, 1, 1);
2572
2573        // Add positive terms
2574        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2575        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 1));
2576
2577        // Add negative term (1/z)
2578        laurent.add_negative_term(Expression::Integer(3), 1);
2579
2580        assert_eq!(laurent.principal_part_order, 1);
2581        assert_eq!(laurent.analytic_part_order, 1);
2582        assert_eq!(laurent.positive_terms.len(), 2);
2583        assert_eq!(laurent.negative_terms.len(), 1);
2584    }
2585
2586    #[test]
2587    fn test_laurent_series_residue() {
2588        let z = Variable::new("z");
2589
2590        // Laurent series: 1/z + 2 + 3z (residue should be 1)
2591        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 1);
2592        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 0));
2593        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(3), 1));
2594        laurent.add_negative_term(Expression::Integer(1), 1);
2595
2596        let res = laurent.residue();
2597        if let Expression::Integer(n) = res {
2598            assert_eq!(n, 1);
2599        } else {
2600            panic!("Expected integer residue, got {:?}", res);
2601        }
2602    }
2603
2604    #[test]
2605    fn test_laurent_series_pole_order() {
2606        let z = Variable::new("z");
2607
2608        // Laurent series: 1/z^3 + 2/z + 1 (pole of order 3)
2609        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 3, 0);
2610        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2611        laurent.add_negative_term(Expression::Integer(1), 3);
2612        laurent.add_negative_term(Expression::Integer(2), 1);
2613
2614        // The principal_part_order stores the pole order
2615        assert_eq!(laurent.principal_part_order, 3);
2616    }
2617
2618    #[test]
2619    fn test_laurent_series_principal_part() {
2620        let z = Variable::new("z");
2621
2622        // Laurent series: 2/z^2 + 3/z + 1 + z
2623        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 2, 1);
2624        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2625        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 1));
2626        laurent.add_negative_term(Expression::Integer(2), 2);
2627        laurent.add_negative_term(Expression::Integer(3), 1);
2628
2629        let principal = laurent.principal_part();
2630        assert_eq!(principal.negative_terms.len(), 2);
2631    }
2632
2633    #[test]
2634    fn test_laurent_series_analytic_part() {
2635        let z = Variable::new("z");
2636
2637        // Laurent series: 1/z + 2 + 3z + 4z^2
2638        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 2);
2639        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 0));
2640        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(3), 1));
2641        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(4), 2));
2642        laurent.add_negative_term(Expression::Integer(1), 1);
2643
2644        let analytic = laurent.analytic_part();
2645        assert_eq!(analytic.term_count(), 3);
2646    }
2647
2648    #[test]
2649    fn test_laurent_series_evaluate() {
2650        let z = Variable::new("z");
2651
2652        // Laurent series: 1/z + 1 + z centered at 0
2653        // At z = 2: 1/2 + 1 + 2 = 3.5
2654        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 1);
2655        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2656        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 1));
2657        laurent.add_negative_term(Expression::Integer(1), 1);
2658
2659        let result = laurent.evaluate(2.0);
2660        assert!(result.is_some());
2661        assert!((result.unwrap() - 3.5).abs() < 1e-10);
2662    }
2663
2664    #[test]
2665    fn test_laurent_series_evaluate_at_singularity() {
2666        let z = Variable::new("z");
2667
2668        // Laurent series: 1/z centered at 0
2669        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 0);
2670        laurent.add_negative_term(Expression::Integer(1), 1);
2671
2672        // Should return None at the singularity
2673        let result = laurent.evaluate(0.0);
2674        assert!(result.is_none());
2675    }
2676
2677    #[test]
2678    fn test_laurent_series_to_latex() {
2679        let z = Variable::new("z");
2680
2681        // Laurent series: 2/z + 1 + 3z centered at 0
2682        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 1);
2683        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2684        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(3), 1));
2685        laurent.add_negative_term(Expression::Integer(2), 1);
2686
2687        let latex = laurent.to_latex();
2688        // Should contain z^{-1} for negative power
2689        assert!(latex.contains("z^{-1}"));
2690    }
2691
2692    #[test]
2693    fn test_laurent_is_taylor() {
2694        let z = Variable::new("z");
2695
2696        // A Taylor series has no negative powers
2697        let mut taylor_like = LaurentSeries::new(z.clone(), Expression::Integer(0), 0, 2);
2698        taylor_like.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2699        taylor_like.add_positive_term(SeriesTerm::new(Expression::Integer(2), 1));
2700
2701        assert!(taylor_like.is_taylor());
2702
2703        // A true Laurent series has negative powers
2704        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 1, 2);
2705        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2706        laurent.add_negative_term(Expression::Integer(1), 1);
2707
2708        assert!(!laurent.is_taylor());
2709    }
2710
2711    #[test]
2712    fn test_singularity_creation() {
2713        let location = Expression::Integer(0);
2714        let singularity = Singularity {
2715            location: location.clone(),
2716            singularity_type: SingularityType::Pole(2),
2717        };
2718
2719        assert!(matches!(
2720            singularity.singularity_type,
2721            SingularityType::Pole(2)
2722        ));
2723    }
2724
2725    #[test]
2726    fn test_find_singularities_simple_pole() {
2727        let z = Variable::new("z");
2728
2729        // f(z) = 1/z has a simple pole at z = 0
2730        let expr = Expression::Binary(
2731            BinaryOp::Div,
2732            Box::new(Expression::Integer(1)),
2733            Box::new(Expression::Variable(z.clone())),
2734        );
2735
2736        let singularities = find_singularities(&expr, &z);
2737
2738        // Should find at least one singularity at z = 0
2739        assert!(!singularities.is_empty());
2740    }
2741
2742    #[test]
2743    fn test_pole_order_simple() {
2744        let z = Variable::new("z");
2745
2746        // 1/z has a simple pole (order 1)
2747        let expr = Expression::Binary(
2748            BinaryOp::Div,
2749            Box::new(Expression::Integer(1)),
2750            Box::new(Expression::Variable(z.clone())),
2751        );
2752
2753        let order = pole_order(&expr, &z, &Expression::Integer(0));
2754        assert!(order.is_ok());
2755        assert_eq!(order.unwrap(), 1);
2756    }
2757
2758    #[test]
2759    fn test_pole_order_double() {
2760        let z = Variable::new("z");
2761
2762        // 1/z^2 has a double pole (order 2)
2763        let z_squared = Expression::Power(
2764            Box::new(Expression::Variable(z.clone())),
2765            Box::new(Expression::Integer(2)),
2766        );
2767        let expr = Expression::Binary(
2768            BinaryOp::Div,
2769            Box::new(Expression::Integer(1)),
2770            Box::new(z_squared),
2771        );
2772
2773        let order = pole_order(&expr, &z, &Expression::Integer(0));
2774        assert!(order.is_ok());
2775        assert_eq!(order.unwrap(), 2);
2776    }
2777
2778    #[test]
2779    fn test_residue_simple_pole() {
2780        let z = Variable::new("z");
2781
2782        // f(z) = 1/z has residue 1 at z = 0
2783        let expr = Expression::Binary(
2784            BinaryOp::Div,
2785            Box::new(Expression::Integer(1)),
2786            Box::new(Expression::Variable(z.clone())),
2787        );
2788
2789        let res = residue(&expr, &z, &Expression::Integer(0));
2790        assert!(res.is_ok());
2791        // Simplified residue should be 1
2792        let res_val = res.unwrap().simplify();
2793        if let Expression::Integer(n) = res_val {
2794            assert_eq!(n, 1);
2795        } else if let Expression::Float(f) = res_val {
2796            assert!((f - 1.0).abs() < 1e-10);
2797        }
2798    }
2799
2800    #[test]
2801    fn test_laurent_function_simple() {
2802        let z = Variable::new("z");
2803
2804        // 1/z Laurent expansion around z=0 should give the series 1/z
2805        let expr = Expression::Binary(
2806            BinaryOp::Div,
2807            Box::new(Expression::Integer(1)),
2808            Box::new(Expression::Variable(z.clone())),
2809        );
2810
2811        // Request expansion with neg_order=1 for a simple pole
2812        let result = laurent(&expr, &z, &Expression::Integer(0), 1, 3);
2813        assert!(result.is_ok());
2814
2815        let laurent_series = result.unwrap();
2816        // Should have one negative term (1/z)
2817        assert!(!laurent_series.negative_terms.is_empty());
2818        // principal_part_order matches requested neg_order
2819        assert_eq!(laurent_series.principal_part_order, 1);
2820    }
2821
2822    #[test]
2823    fn test_laurent_to_taylor() {
2824        let z = Variable::new("z");
2825
2826        // A Laurent series without negative powers can convert to Taylor
2827        let mut laurent = LaurentSeries::new(z.clone(), Expression::Integer(0), 0, 2);
2828        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(1), 0));
2829        laurent.add_positive_term(SeriesTerm::new(Expression::Integer(2), 1));
2830
2831        let taylor_opt = laurent.to_taylor();
2832        assert!(taylor_opt.is_some());
2833
2834        let taylor = taylor_opt.unwrap();
2835        assert_eq!(taylor.term_count(), 2);
2836    }
2837
2838    // Series Arithmetic Tests
2839
2840    #[test]
2841    fn test_series_addition() {
2842        let x = Variable::new("x");
2843
2844        // Series 1: 1 + x + x^2
2845        let mut s1 = Series::new(x.clone(), Expression::Integer(0), 3);
2846        s1.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2847        s1.add_term(SeriesTerm::new(Expression::Integer(1), 1));
2848        s1.add_term(SeriesTerm::new(Expression::Integer(1), 2));
2849
2850        // Series 2: 2 + 3x + x^2
2851        let mut s2 = Series::new(x.clone(), Expression::Integer(0), 3);
2852        s2.add_term(SeriesTerm::new(Expression::Integer(2), 0));
2853        s2.add_term(SeriesTerm::new(Expression::Integer(3), 1));
2854        s2.add_term(SeriesTerm::new(Expression::Integer(1), 2));
2855
2856        // Sum should be: 3 + 4x + 2x^2
2857        let sum = (s1 + s2).unwrap();
2858
2859        // Evaluate at x = 0.5: 3 + 4*0.5 + 2*0.25 = 3 + 2 + 0.5 = 5.5
2860        let result = sum.evaluate(0.5).unwrap();
2861        assert!((result - 5.5).abs() < 1e-10);
2862    }
2863
2864    #[test]
2865    fn test_series_subtraction() {
2866        let x = Variable::new("x");
2867
2868        // Series 1: 3 + 2x + x^2
2869        let mut s1 = Series::new(x.clone(), Expression::Integer(0), 2);
2870        s1.add_term(SeriesTerm::new(Expression::Integer(3), 0));
2871        s1.add_term(SeriesTerm::new(Expression::Integer(2), 1));
2872        s1.add_term(SeriesTerm::new(Expression::Integer(1), 2));
2873
2874        // Series 2: 1 + x + x^2
2875        let mut s2 = Series::new(x.clone(), Expression::Integer(0), 2);
2876        s2.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2877        s2.add_term(SeriesTerm::new(Expression::Integer(1), 1));
2878        s2.add_term(SeriesTerm::new(Expression::Integer(1), 2));
2879
2880        // Difference should be: 2 + x
2881        let diff = (s1 - s2).unwrap();
2882
2883        // Evaluate at x = 1: 2 + 1 = 3
2884        let result = diff.evaluate(1.0).unwrap();
2885        assert!((result - 3.0).abs() < 1e-10);
2886    }
2887
2888    #[test]
2889    fn test_series_multiplication() {
2890        let x = Variable::new("x");
2891
2892        // (1 + x) * (1 - x) = 1 - x^2
2893        let mut s1 = Series::new(x.clone(), Expression::Integer(0), 4);
2894        s1.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2895        s1.add_term(SeriesTerm::new(Expression::Integer(1), 1));
2896
2897        let mut s2 = Series::new(x.clone(), Expression::Integer(0), 4);
2898        s2.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2899        s2.add_term(SeriesTerm::new(Expression::Integer(-1), 1));
2900
2901        let product = (s1 * s2).unwrap();
2902
2903        // Evaluate at x = 0.5: 1 - 0.25 = 0.75
2904        let result = product.evaluate(0.5).unwrap();
2905        assert!((result - 0.75).abs() < 1e-10);
2906    }
2907
2908    #[test]
2909    fn test_series_reciprocal() {
2910        let x = Variable::new("x");
2911
2912        // 1/(1-x) = 1 + x + x^2 + x^3 + ... (geometric series)
2913        let mut s = Series::new(x.clone(), Expression::Integer(0), 5);
2914        s.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2915        s.add_term(SeriesTerm::new(Expression::Integer(-1), 1));
2916
2917        let recip = s.reciprocal().unwrap();
2918
2919        // Evaluate at x = 0.5: 1/(1-0.5) = 2
2920        // Series approximation: 1 + 0.5 + 0.25 + 0.125 + 0.0625 + 0.03125 ≈ 1.96875
2921        let result = recip.evaluate(0.5).unwrap();
2922        assert!((result - 1.96875).abs() < 1e-10);
2923    }
2924
2925    #[test]
2926    fn test_series_division() {
2927        let x = Variable::new("x");
2928
2929        // (1 - x^2) / (1 - x) = 1 + x
2930        let mut num = Series::new(x.clone(), Expression::Integer(0), 4);
2931        num.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2932        num.add_term(SeriesTerm::new(Expression::Integer(-1), 2));
2933
2934        let mut denom = Series::new(x.clone(), Expression::Integer(0), 4);
2935        denom.add_term(SeriesTerm::new(Expression::Integer(1), 0));
2936        denom.add_term(SeriesTerm::new(Expression::Integer(-1), 1));
2937
2938        let quotient = (num / denom).unwrap();
2939
2940        // Evaluate at x = 0.5: 1 + 0.5 = 1.5
2941        let result = quotient.evaluate(0.5).unwrap();
2942        assert!((result - 1.5).abs() < 0.01);
2943    }
2944
2945    #[test]
2946    fn test_series_differentiate() {
2947        let x = Variable::new("x");
2948
2949        // d/dx[1 + x + x^2/2 + x^3/6] = 1 + x + x^2/2 (e^x derivative is e^x)
2950        let exp_series = exp_series(&x, 4);
2951        let deriv = exp_series.differentiate();
2952
2953        // d/dx[e^x] at x=0.1 should ≈ e^{0.1} ≈ 1.10517
2954        let result = deriv.evaluate(0.1).unwrap();
2955        let expected = 0.1_f64.exp();
2956        assert!((result - expected).abs() < 0.01);
2957    }
2958
2959    #[test]
2960    fn test_series_integrate() {
2961        let x = Variable::new("x");
2962
2963        // integral[1 + x + x^2/2!] dx with C=0 should give x + x^2/2 + x^3/6
2964        // This is integrating the first few terms of e^x
2965        let exp_series = exp_series(&x, 3);
2966        let integrated = exp_series.integrate(Expression::Integer(0));
2967
2968        // At x=0.1: 0.1 + 0.01/2 + 0.001/6 ≈ 0.10517
2969        let result = integrated.evaluate(0.1).unwrap();
2970        let expected = 0.1_f64.exp() - 1.0; // integral of e^x from 0 to 0.1 is e^0.1 - 1
2971        assert!((result - expected).abs() < 0.01);
2972    }
2973
2974    #[test]
2975    fn test_differentiate_then_integrate() {
2976        let x = Variable::new("x");
2977
2978        // Differentiate sin(x), then integrate should give back sin(x) (up to constant)
2979        let sin_s = sin_series(&x, 7);
2980        let deriv = sin_s.differentiate(); // Should be cos(x) series
2981        let integrated = deriv.integrate(Expression::Integer(0)); // Back to sin(x)
2982
2983        // At x=0.5: sin(0.5) ≈ 0.4794
2984        let result = integrated.evaluate(0.5).unwrap();
2985        let expected = 0.5_f64.sin();
2986        assert!((result - expected).abs() < 0.01);
2987    }
2988
2989    #[test]
2990    fn test_exp_times_neg_exp() {
2991        let x = Variable::new("x");
2992
2993        // e^x * e^{-x} = 1 (should cancel to 1)
2994        let exp_pos = exp_series(&x, 5);
2995
2996        // Build e^{-x} = 1 - x + x^2/2 - x^3/6 + ...
2997        let mut exp_neg = Series::new(x.clone(), Expression::Integer(0), 5);
2998        for n in 0..=5 {
2999            let sign = if n % 2 == 0 { 1.0 } else { -1.0 };
3000            let coeff = sign / factorial(n) as f64;
3001            exp_neg.add_term(SeriesTerm::new(Expression::Float(coeff), n));
3002        }
3003
3004        let product = (exp_pos * exp_neg).unwrap();
3005
3006        // At any x, e^x * e^{-x} = 1
3007        let result = product.evaluate(0.5).unwrap();
3008        assert!((result - 1.0).abs() < 0.01);
3009    }
3010
3011    #[test]
3012    fn test_compose_series_exp_sin() {
3013        let x = Variable::new("x");
3014
3015        // e^{sin(x)} composition
3016        let exp_s = exp_series(&x, 5);
3017        let sin_s = sin_series(&x, 5);
3018
3019        // sin(x) has no constant term, so composition is valid
3020        let composed = compose_series(&exp_s, &sin_s).unwrap();
3021
3022        // e^{sin(0.1)} = e^{0.0998...} ≈ 1.1049
3023        let result = composed.evaluate(0.1).unwrap();
3024        let expected = (0.1_f64.sin()).exp();
3025        assert!((result - expected).abs() < 0.01);
3026    }
3027
3028    #[test]
3029    fn test_series_reversion() {
3030        let x = Variable::new("x");
3031
3032        // sin(x) = x - x^3/6 + ...
3033        // arcsin(x) is the reversion of sin(x)
3034        let sin_s = sin_series(&x, 7);
3035        let arcsin_s = reversion(&sin_s).unwrap();
3036
3037        // arcsin(0.5) ≈ 0.5236 (π/6)
3038        let result = arcsin_s.evaluate(0.5).unwrap();
3039        let expected = 0.5_f64.asin();
3040        assert!((result - expected).abs() < 0.05);
3041    }
3042
3043    // Asymptotic Expansion Tests
3044
3045    #[test]
3046    fn test_asymptotic_direction_display() {
3047        assert_eq!(format!("{}", AsymptoticDirection::PosInfinity), "x→+∞");
3048        assert_eq!(format!("{}", AsymptoticDirection::NegInfinity), "x→-∞");
3049        assert_eq!(format!("{}", AsymptoticDirection::Zero), "x→0");
3050    }
3051
3052    #[test]
3053    fn test_asymptotic_term_creation() {
3054        let term = AsymptoticTerm::new(Expression::Integer(2), Expression::Integer(-1));
3055        assert_eq!(term.coefficient, Expression::Integer(2));
3056        assert_eq!(term.exponent, Expression::Integer(-1));
3057        assert!(!term.is_zero());
3058
3059        let zero_term = AsymptoticTerm::new(Expression::Integer(0), Expression::Integer(1));
3060        assert!(zero_term.is_zero());
3061    }
3062
3063    #[test]
3064    fn test_asymptotic_term_evaluate() {
3065        let x = Variable::new("x");
3066
3067        // 2*x^(-1) at x=4 should be 2/4 = 0.5
3068        let term = AsymptoticTerm::new(Expression::Integer(2), Expression::Integer(-1));
3069        let result = term.evaluate(&x, 4.0).unwrap();
3070        assert!((result - 0.5).abs() < 1e-10);
3071
3072        // 3*x^2 at x=2 should be 3*4 = 12
3073        let term2 = AsymptoticTerm::new(Expression::Integer(3), Expression::Integer(2));
3074        let result2 = term2.evaluate(&x, 2.0).unwrap();
3075        assert!((result2 - 12.0).abs() < 1e-10);
3076    }
3077
3078    #[test]
3079    fn test_asymptotic_term_display() {
3080        let term1 = AsymptoticTerm::new(Expression::Integer(2), Expression::Integer(0));
3081        assert_eq!(format!("{}", term1), "2");
3082
3083        let term2 = AsymptoticTerm::new(Expression::Integer(3), Expression::Integer(1));
3084        assert_eq!(format!("{}", term2), "3·x");
3085
3086        let term3 = AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(-2));
3087        assert_eq!(format!("{}", term3), "1/x^2");
3088    }
3089
3090    #[test]
3091    fn test_big_o_creation() {
3092        let x = Variable::new("x");
3093        let order = Expression::Power(
3094            Box::new(Expression::Variable(x.clone())),
3095            Box::new(Expression::Integer(2)),
3096        );
3097        let big_o = BigO::new(order.clone(), x.clone());
3098
3099        assert_eq!(big_o.order, order);
3100        assert_eq!(big_o.variable, x);
3101    }
3102
3103    #[test]
3104    fn test_big_o_is_same_order() {
3105        let x = Variable::new("x");
3106        let order1 = Expression::Power(
3107            Box::new(Expression::Variable(x.clone())),
3108            Box::new(Expression::Integer(2)),
3109        );
3110        let order2 = Expression::Power(
3111            Box::new(Expression::Variable(x.clone())),
3112            Box::new(Expression::Integer(2)),
3113        );
3114
3115        let big_o1 = BigO::new(order1, x.clone());
3116        let big_o2 = BigO::new(order2, x.clone());
3117
3118        assert!(big_o1.is_same_order(&big_o2));
3119    }
3120
3121    #[test]
3122    fn test_big_o_display() {
3123        let x = Variable::new("x");
3124        let order = Expression::Power(
3125            Box::new(Expression::Variable(x.clone())),
3126            Box::new(Expression::Integer(3)),
3127        );
3128        let big_o = BigO::new(order, x);
3129
3130        assert!(format!("{}", big_o).contains("O("));
3131    }
3132
3133    #[test]
3134    fn test_asymptotic_series_creation() {
3135        let x = Variable::new("x");
3136        let series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3137
3138        assert_eq!(series.variable, x);
3139        assert_eq!(series.direction, AsymptoticDirection::PosInfinity);
3140        assert_eq!(series.terms.len(), 0);
3141    }
3142
3143    #[test]
3144    fn test_asymptotic_series_add_term() {
3145        let x = Variable::new("x");
3146        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3147
3148        series.add_term(AsymptoticTerm::new(
3149            Expression::Integer(1),
3150            Expression::Integer(-1),
3151        ));
3152        series.add_term(AsymptoticTerm::new(
3153            Expression::Integer(1),
3154            Expression::Integer(-2),
3155        ));
3156
3157        assert_eq!(series.terms.len(), 2);
3158    }
3159
3160    #[test]
3161    fn test_asymptotic_series_dominant_term() {
3162        let x = Variable::new("x");
3163        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3164
3165        series.add_term(AsymptoticTerm::new(
3166            Expression::Integer(1),
3167            Expression::Integer(-1),
3168        ));
3169        series.add_term(AsymptoticTerm::new(
3170            Expression::Integer(1),
3171            Expression::Integer(-2),
3172        ));
3173
3174        let dominant = series.dominant_term().unwrap();
3175        assert_eq!(dominant.exponent, Expression::Integer(-1));
3176    }
3177
3178    #[test]
3179    fn test_asymptotic_series_order_of_magnitude() {
3180        let x = Variable::new("x");
3181        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3182
3183        series.add_term(AsymptoticTerm::new(
3184            Expression::Integer(2),
3185            Expression::Integer(-1),
3186        ));
3187
3188        let order = series.order_of_magnitude().unwrap();
3189        assert_eq!(order, Expression::Integer(-1));
3190    }
3191
3192    #[test]
3193    fn test_asymptotic_series_with_error_term() {
3194        let x = Variable::new("x");
3195        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3196
3197        series.add_term(AsymptoticTerm::new(
3198            Expression::Integer(1),
3199            Expression::Integer(-1),
3200        ));
3201        series.add_term(AsymptoticTerm::new(
3202            Expression::Integer(1),
3203            Expression::Integer(-2),
3204        ));
3205
3206        let (_, big_o) = series.with_error_term();
3207        // Error term should be O(x^(-3)) for x→∞ when last term is x^(-2)
3208        assert_eq!(big_o.variable, x);
3209    }
3210
3211    #[test]
3212    fn test_asymptotic_series_evaluate() {
3213        let x = Variable::new("x");
3214        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3215
3216        // 1/x + 1/x^2 at x=2 should be 0.5 + 0.25 = 0.75
3217        series.add_term(AsymptoticTerm::new(
3218            Expression::Integer(1),
3219            Expression::Integer(-1),
3220        ));
3221        series.add_term(AsymptoticTerm::new(
3222            Expression::Integer(1),
3223            Expression::Integer(-2),
3224        ));
3225
3226        let result = series.evaluate(2.0).unwrap();
3227        assert!((result - 0.75).abs() < 1e-10);
3228    }
3229
3230    #[test]
3231    fn test_asymptotic_1_over_x() {
3232        use crate::parser::parse_expression;
3233
3234        // 1/x as x→∞ should give [1/x]
3235        let expr = parse_expression("1/x").unwrap();
3236        let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
3237
3238        assert_eq!(series.terms.len(), 1);
3239        assert_eq!(series.terms[0].coefficient, Expression::Integer(1));
3240        assert_eq!(series.terms[0].exponent, Expression::Integer(-1));
3241    }
3242
3243    #[test]
3244    fn test_asymptotic_1_over_x_plus_1_over_x2() {
3245        use crate::parser::parse_expression;
3246
3247        // 1/x + 1/x^2 as x→∞
3248        let expr = parse_expression("1/x + 1/x^2").unwrap();
3249        let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
3250
3251        assert_eq!(series.terms.len(), 2);
3252        // Dominant term should be 1/x (exponent -1)
3253        assert_eq!(series.terms[0].exponent, Expression::Integer(-1));
3254
3255        // Next term should be 1/x^2 (exponent -2)
3256        // The exponent might be Unary(Neg, Float(2.0)) or Integer(-2) depending on simplification
3257        let exp1 = &series.terms[1].exponent;
3258        let exp1_val = try_expr_to_f64(exp1).unwrap();
3259        assert!((exp1_val - (-2.0)).abs() < 1e-10);
3260    }
3261
3262    #[test]
3263    fn test_asymptotic_x_squared_plus_x() {
3264        use crate::parser::parse_expression;
3265
3266        // x^2 + x as x→∞, dominant term is x^2
3267        let expr = parse_expression("x^2 + x").unwrap();
3268        let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
3269
3270        let dominant = series.dominant_term().unwrap();
3271        // Exponent might be Integer(2) or Float(2.0) depending on parser
3272        let exp_val = try_expr_to_f64(&dominant.exponent).unwrap();
3273        assert!((exp_val - 2.0).abs() < 1e-10);
3274    }
3275
3276    #[test]
3277    fn test_asymptotic_evaluate_at_point() {
3278        use crate::parser::parse_expression;
3279
3280        // 1/x + 1/x^2 at x=10 should be 0.1 + 0.01 = 0.11
3281        let expr = parse_expression("1/x + 1/x^2").unwrap();
3282        let series = asymptotic(&expr, "x", AsymptoticDirection::PosInfinity, 3).unwrap();
3283
3284        let result = series.evaluate(10.0).unwrap();
3285        assert!((result - 0.11).abs() < 1e-10);
3286    }
3287
3288    #[test]
3289    fn test_sort_by_dominance_pos_infinity() {
3290        let _x = Variable::new("x");
3291        let mut terms = vec![
3292            AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(-2)),
3293            AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(-1)),
3294            AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(0)),
3295        ];
3296
3297        sort_by_dominance(&mut terms, AsymptoticDirection::PosInfinity);
3298
3299        // For x→∞, constant (0) > 1/x (-1) > 1/x^2 (-2)
3300        assert_eq!(terms[0].exponent, Expression::Integer(0));
3301        assert_eq!(terms[1].exponent, Expression::Integer(-1));
3302        assert_eq!(terms[2].exponent, Expression::Integer(-2));
3303    }
3304
3305    #[test]
3306    fn test_sort_by_dominance_zero() {
3307        let _x = Variable::new("x");
3308        let mut terms = vec![
3309            AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(2)),
3310            AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(1)),
3311            AsymptoticTerm::new(Expression::Integer(1), Expression::Integer(0)),
3312        ];
3313
3314        sort_by_dominance(&mut terms, AsymptoticDirection::Zero);
3315
3316        // For x→0, constant (0) > x (1) > x^2 (2)
3317        assert_eq!(terms[0].exponent, Expression::Integer(0));
3318        assert_eq!(terms[1].exponent, Expression::Integer(1));
3319        assert_eq!(terms[2].exponent, Expression::Integer(2));
3320    }
3321
3322    #[test]
3323    fn test_limit_via_asymptotic_to_zero() {
3324        use crate::limits::LimitResult;
3325        use crate::parser::parse_expression;
3326
3327        // lim_{x→∞} 1/x = 0
3328        let expr = parse_expression("1/x").unwrap();
3329        let result = limit_via_asymptotic(&expr, "x", AsymptoticDirection::PosInfinity).unwrap();
3330
3331        assert_eq!(result, LimitResult::Value(0.0));
3332    }
3333
3334    #[test]
3335    fn test_limit_via_asymptotic_to_infinity() {
3336        use crate::limits::LimitResult;
3337        use crate::parser::parse_expression;
3338
3339        // lim_{x→∞} x^2 = ∞
3340        let expr = parse_expression("x^2").unwrap();
3341        let result = limit_via_asymptotic(&expr, "x", AsymptoticDirection::PosInfinity).unwrap();
3342
3343        assert_eq!(result, LimitResult::PositiveInfinity);
3344    }
3345
3346    #[test]
3347    fn test_limit_via_asymptotic_constant() {
3348        use crate::limits::LimitResult;
3349
3350        // lim_{x→∞} 5 = 5
3351        let expr = Expression::Integer(5);
3352        let result = limit_via_asymptotic(&expr, "x", AsymptoticDirection::PosInfinity).unwrap();
3353
3354        assert_eq!(result, LimitResult::Value(5.0));
3355    }
3356
3357    #[test]
3358    fn test_asymptotic_series_to_expression() {
3359        let x = Variable::new("x");
3360        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3361
3362        series.add_term(AsymptoticTerm::new(
3363            Expression::Integer(1),
3364            Expression::Integer(-1),
3365        ));
3366        series.add_term(AsymptoticTerm::new(
3367            Expression::Integer(2),
3368            Expression::Integer(-2),
3369        ));
3370
3371        let expr = series.to_expression();
3372        // Should be simplifiable to some form
3373        assert!(!matches!(expr, Expression::Integer(0)));
3374    }
3375
3376    #[test]
3377    fn test_asymptotic_series_display() {
3378        let x = Variable::new("x");
3379        let mut series = AsymptoticSeries::new(x.clone(), AsymptoticDirection::PosInfinity);
3380
3381        series.add_term(AsymptoticTerm::new(
3382            Expression::Integer(1),
3383            Expression::Integer(-1),
3384        ));
3385
3386        let display_str = format!("{}", series);
3387        assert!(display_str.contains("x→+∞"));
3388    }
3389}