mathhook_core/calculus/residues/
singularities.rs

1//! Singularity classification for complex analysis
2//!
3//! Implements classification of singularities into removable, poles, and essential types.
4
5use crate::calculus::limits::Limits;
6use crate::core::{Expression, Symbol};
7
8use super::helpers::{is_expression_zero, is_finite};
9
10/// Types of singularities in complex analysis
11#[derive(Debug, Clone, PartialEq)]
12pub enum SingularityType {
13    /// Removable singularity (limit exists and is finite)
14    Removable,
15    /// Pole of given order
16    Pole(u32),
17    /// Essential singularity (neither removable nor pole)
18    Essential,
19    /// Unable to classify
20    Unknown,
21}
22
23/// Trait for complex analysis operations related to singularities
24pub trait ComplexAnalysis {
25    /// Check if function is analytic at a point
26    ///
27    /// # Examples
28    ///
29    /// ```rust
30    /// use mathhook_core::{Expression, symbol};
31    /// use mathhook_core::calculus::ComplexAnalysis;
32    ///
33    /// let z = symbol!(z);
34    /// let expr = Expression::pow(Expression::symbol(z.clone()), Expression::integer(2));
35    /// let point = Expression::integer(1);
36    /// let is_analytic = expr.is_analytic_at(&z, &point);
37    /// ```
38    fn is_analytic_at(&self, variable: &Symbol, point: &Expression) -> bool;
39
40    /// Determine the order of a pole
41    ///
42    /// # Examples
43    ///
44    /// ```rust
45    /// use mathhook_core::{Expression, symbol};
46    /// use mathhook_core::calculus::ComplexAnalysis;
47    ///
48    /// let z = symbol!(z);
49    /// let expr = Expression::pow(
50    ///     Expression::pow(
51    ///         Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-1)]),
52    ///         Expression::integer(2)
53    ///     ),
54    ///     Expression::integer(-1)
55    /// );
56    /// let pole = Expression::integer(1);
57    /// let order = expr.pole_order(&z, &pole);
58    /// ```
59    fn pole_order(&self, variable: &Symbol, pole: &Expression) -> u32;
60
61    /// Check if point is a removable singularity
62    ///
63    /// # Examples
64    ///
65    /// ```rust
66    /// use mathhook_core::{Expression, symbol};
67    /// use mathhook_core::calculus::ComplexAnalysis;
68    ///
69    /// let z = symbol!(z);
70    /// let expr = Expression::mul(vec![
71    ///     Expression::function("sin", vec![Expression::symbol(z.clone())]),
72    ///     Expression::pow(Expression::symbol(z.clone()), Expression::integer(-1))
73    /// ]);
74    /// let point = Expression::integer(0);
75    /// let is_removable = expr.is_removable_singularity(&z, &point);
76    /// ```
77    fn is_removable_singularity(&self, variable: &Symbol, point: &Expression) -> bool;
78
79    /// Check if point is an essential singularity
80    ///
81    /// # Examples
82    ///
83    /// ```rust
84    /// use mathhook_core::{Expression, symbol};
85    /// use mathhook_core::calculus::ComplexAnalysis;
86    ///
87    /// let z = symbol!(z);
88    /// let expr = Expression::function(
89    ///     "exp",
90    ///     vec![Expression::pow(Expression::symbol(z.clone()), Expression::integer(-1))]
91    /// );
92    /// let point = Expression::integer(0);
93    /// let is_essential = expr.is_essential_singularity(&z, &point);
94    /// ```
95    fn is_essential_singularity(&self, variable: &Symbol, point: &Expression) -> bool;
96}
97
98impl ComplexAnalysis for Expression {
99    fn is_analytic_at(&self, variable: &Symbol, point: &Expression) -> bool {
100        use crate::calculus::ResidueCalculus;
101        // A function is analytic if it has no singularities
102        let poles = self.find_poles(variable);
103        !poles.contains(point)
104    }
105
106    fn pole_order(&self, variable: &Symbol, pole: &Expression) -> u32 {
107        use crate::calculus::derivatives::Derivative;
108        use crate::calculus::integrals::rational::helpers::substitute_variable;
109        use crate::simplify::Simplify;
110
111        use super::helpers::extract_denominator;
112
113        // Extract the denominator from the expression
114        let denominator = match extract_denominator(self) {
115            Some(denom) => denom,
116            None => return 0, // Not a rational function, no pole
117        };
118
119        // Check if the denominator is actually zero at the pole point
120        let denom_at_pole = substitute_variable(&denominator, variable, pole).simplify();
121        if !is_expression_zero(&denom_at_pole) {
122            return 0; // Denominator is non-zero at this point, so no pole
123        }
124
125        // Special case: denominator is (expr)^n where expr evaluates to zero at pole
126        if let Expression::Pow(base, exp) = &denominator {
127            // Check if base is zero at the pole
128            let base_at_pole = substitute_variable(base, variable, pole).simplify();
129            if is_expression_zero(&base_at_pole) {
130                // Extract the exponent if it's a positive integer
131                if let Expression::Number(crate::core::Number::Integer(n)) = exp.as_ref() {
132                    if *n > 0 {
133                        return *n as u32;
134                    }
135                }
136            }
137        }
138
139        // General case: Use repeated differentiation
140        const MAX_ORDER: u32 = 100;
141        let mut current_deriv = denominator;
142
143        for order in 1..=MAX_ORDER {
144            current_deriv = current_deriv.derivative(variable.clone());
145            let deriv_at_pole = substitute_variable(&current_deriv, variable, pole).simplify();
146
147            if !is_expression_zero(&deriv_at_pole) {
148                return order;
149            }
150        }
151
152        // If we reach here, either it's an essential singularity or numerical issues
153        0
154    }
155
156    fn is_removable_singularity(&self, variable: &Symbol, point: &Expression) -> bool {
157        // A singularity is removable if:
158        // 1. The function is not defined at the point (has singularity)
159        // 2. BUT lim(z→point) f(z) exists and is finite
160
161        // Compute the limit at the point
162        let limit_result = self.limit(variable, point);
163
164        // Check if the limit is finite (not infinity, not undefined)
165        is_finite(&limit_result)
166    }
167
168    fn is_essential_singularity(&self, variable: &Symbol, point: &Expression) -> bool {
169        // Essential singularities occur when a function has infinitely many
170        // negative power terms in its Laurent series expansion.
171        // Common patterns: exp(1/z), sin(1/z), cos(1/z) at z=0
172
173        // Check for common essential singularity patterns
174        match self {
175            // exp(1/(z-point)) at z=point is an essential singularity
176            Expression::Function { name, args } if name.as_ref() == "exp" && args.len() == 1 => {
177                has_reciprocal_of_shifted_variable(&args[0], variable, point)
178            }
179            // sin(1/(z-point)) at z=point is an essential singularity
180            Expression::Function { name, args } if name.as_ref() == "sin" && args.len() == 1 => {
181                has_reciprocal_of_shifted_variable(&args[0], variable, point)
182            }
183            // cos(1/(z-point)) at z=point is an essential singularity
184            Expression::Function { name, args } if name.as_ref() == "cos" && args.len() == 1 => {
185                has_reciprocal_of_shifted_variable(&args[0], variable, point)
186            }
187            // log(z-point) at z=point is a logarithmic singularity (branch point)
188            Expression::Function { name, args } if name.as_ref() == "log" && args.len() == 1 => {
189                is_shifted_variable(&args[0], variable, point)
190            }
191            _ => false,
192        }
193    }
194}
195
196/// Check if expression is 1/(variable - point)
197///
198/// # Arguments
199///
200/// * `expr` - The expression to check
201/// * `variable` - The variable symbol
202/// * `point` - The point to check against
203///
204/// # Returns
205///
206/// `true` if expr represents 1/(variable - point), `false` otherwise
207fn has_reciprocal_of_shifted_variable(
208    expr: &Expression,
209    variable: &Symbol,
210    point: &Expression,
211) -> bool {
212    use crate::calculus::integrals::rational::helpers::substitute_variable;
213    use crate::simplify::Simplify;
214
215    use super::helpers::{is_infinity, is_undefined};
216
217    // Check for (variable - point)^(-1) or similar patterns
218    match expr {
219        // Direct power: (z-point)^(-1)
220        Expression::Pow(base, exp) => {
221            if let Expression::Number(crate::core::Number::Integer(n)) = exp.as_ref() {
222                if *n == -1 && is_shifted_variable(base, variable, point) {
223                    return true;
224                }
225            }
226            false
227        }
228        // Multiplication form: constant * (z-point)^(-1)
229        Expression::Mul(factors) => factors
230            .iter()
231            .any(|f| has_reciprocal_of_shifted_variable(f, variable, point)),
232        // Check if it simplifies to the pattern
233        _ => {
234            // Try evaluating at a test point near the singularity
235            let test_expr = substitute_variable(expr, variable, point);
236            is_infinity(&test_expr.simplify()) || is_undefined(&test_expr.simplify())
237        }
238    }
239}
240
241/// Check if expression equals (variable - point)
242///
243/// # Arguments
244///
245/// * `expr` - The expression to check
246/// * `variable` - The variable symbol
247/// * `point` - The point to check against
248///
249/// # Returns
250///
251/// `true` if expr represents (variable - point), `false` otherwise
252fn is_shifted_variable(expr: &Expression, variable: &Symbol, point: &Expression) -> bool {
253    // Check if expr is of the form (variable - point)
254    match expr {
255        // Direct variable match (when point is 0)
256        Expression::Symbol(s) if s == variable && is_expression_zero(point) => true,
257        // Addition: variable + (-point)
258        Expression::Add(terms) if terms.len() == 2 => {
259            let has_variable = terms
260                .iter()
261                .any(|t| matches!(t, Expression::Symbol(s) if s == variable));
262            let has_negative_point = terms.iter().any(|t| {
263                matches!(t, Expression::Mul(factors) if factors.len() == 2
264                    && matches!(&factors[0], Expression::Number(n) if *n == crate::core::Number::Integer(-1))
265                    && &factors[1] == point)
266            });
267            has_variable && has_negative_point
268        }
269        _ => false,
270    }
271}
272
273/// Determine singularity type at a point
274///
275/// Uses pole order analysis and limit evaluation to classify singularities:
276/// - Pole: `pole_order() > 0`
277/// - Removable: limit exists and is finite
278/// - Essential: neither pole nor removable (e.g., exp(1/z) at z=0)
279///
280/// # Arguments
281///
282/// * `expr` - The expression to analyze
283/// * `variable` - The variable for the singularity
284/// * `point` - The point to classify the singularity at
285///
286/// # Returns
287///
288/// The type of singularity: Removable, Pole(order), Essential, or Unknown
289pub fn classify_singularity(
290    expr: &Expression,
291    variable: &Symbol,
292    point: &Expression,
293) -> SingularityType {
294    // First check if it's a pole
295    let order = expr.pole_order(variable, point);
296    if order > 0 {
297        return SingularityType::Pole(order);
298    }
299
300    // Check if it's a removable singularity
301    if expr.is_removable_singularity(variable, point) {
302        return SingularityType::Removable;
303    }
304
305    // Check if it's an essential singularity
306    if expr.is_essential_singularity(variable, point) {
307        return SingularityType::Essential;
308    }
309
310    // Unable to classify
311    SingularityType::Unknown
312}