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(¤t_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}