mathhook_core/calculus/
residues.rs

1//! Residue calculus and complex analysis
2//!
3//! Implements residue computation, contour integration,
4//! and complex analysis operations for symbolic computation.
5//!
6//! This module is organized into focused sub-modules:
7//! - Core residue computation and integration (in mod.rs)
8//! - Singularity classification (singularities.rs)
9//! - Pole finding for various function types (pole_finding.rs)
10//! - Helper utilities (helpers.rs)
11
12mod helpers;
13mod pole_finding;
14mod singularities;
15
16use crate::calculus::derivatives::Derivative;
17use crate::core::{Expression, Symbol};
18use crate::simplify::Simplify;
19
20use pole_finding::{find_rational_poles, find_transcendental_poles};
21
22// Re-export public API
23pub use singularities::{classify_singularity, ComplexAnalysis, SingularityType};
24
25/// Trait for residue calculus operations
26pub trait ResidueCalculus {
27    /// Compute residue at a pole
28    ///
29    /// # Examples
30    ///
31    /// ```rust
32    /// use mathhook_core::{Expression, symbol};
33    /// use mathhook_core::calculus::ResidueCalculus;
34    ///
35    /// let z = symbol!(z);
36    /// let expr = Expression::pow(
37    ///     Expression::add(vec![
38    ///         Expression::symbol(z.clone()),
39    ///         Expression::integer(-1)
40    ///     ]),
41    ///     Expression::integer(-1)
42    /// );
43    /// let pole = Expression::integer(1);
44    /// let result = expr.residue(&z, &pole);
45    /// ```
46    fn residue(&self, variable: &Symbol, pole: &Expression) -> Expression;
47
48    /// Find all poles of the function
49    ///
50    /// # Examples
51    ///
52    /// ```rust
53    /// use mathhook_core::{Expression, symbol};
54    /// use mathhook_core::calculus::ResidueCalculus;
55    ///
56    /// let z = symbol!(z);
57    /// let expr = Expression::pow(
58    ///     Expression::mul(vec![
59    ///         Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-1)]),
60    ///         Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-2)])
61    ///     ]),
62    ///     Expression::integer(-1)
63    /// );
64    /// let poles = expr.find_poles(&z);
65    /// ```
66    fn find_poles(&self, variable: &Symbol) -> Vec<Expression>;
67
68    /// Compute contour integral using residue theorem
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// use mathhook_core::{Expression, symbol};
74    /// use mathhook_core::calculus::ResidueCalculus;
75    ///
76    /// let z = symbol!(z);
77    /// let expr = Expression::pow(
78    ///     Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-1)]),
79    ///     Expression::integer(-1)
80    /// );
81    /// let result = expr.contour_integral(&z);
82    /// ```
83    fn contour_integral(&self, variable: &Symbol) -> Expression;
84}
85
86/// Residue computation methods
87pub struct ResidueMethods;
88
89impl ResidueMethods {
90    /// Compute residue for simple pole using limit formula
91    ///
92    /// # Arguments
93    ///
94    /// * `numerator` - Numerator of the rational function
95    /// * `denominator` - Denominator of the rational function
96    /// * `variable` - The variable symbol
97    /// * `pole` - Location of the pole
98    ///
99    /// # Returns
100    ///
101    /// Expression representing the residue computation
102    pub fn simple_pole_residue(
103        numerator: &Expression,
104        denominator: &Expression,
105        variable: &Symbol,
106        pole: &Expression,
107    ) -> Expression {
108        // Res(f, a) = lim_{z→a} (z-a)f(z) for simple pole
109        let z_minus_a = Expression::add(vec![
110            Expression::symbol(variable.clone()),
111            Expression::mul(vec![Expression::integer(-1), pole.clone()]),
112        ]);
113
114        let limit_expr = Expression::mul(vec![
115            z_minus_a,
116            Expression::mul(vec![
117                numerator.clone(),
118                Expression::pow(denominator.clone(), Expression::integer(-1)),
119            ]),
120        ]);
121
122        Expression::function(
123            "limit",
124            vec![
125                limit_expr,
126                Expression::symbol(variable.clone()),
127                pole.clone(),
128            ],
129        )
130    }
131
132    /// Compute residue for pole of order m using derivative formula
133    ///
134    /// # Arguments
135    ///
136    /// * `numerator` - Numerator of the rational function
137    /// * `denominator` - Denominator of the rational function
138    /// * `variable` - The variable symbol
139    /// * `pole` - Location of the pole
140    /// * `order` - Order of the pole
141    ///
142    /// # Returns
143    ///
144    /// Expression representing the residue
145    pub fn higher_order_pole_residue(
146        numerator: &Expression,
147        denominator: &Expression,
148        variable: &Symbol,
149        pole: &Expression,
150        order: u32,
151    ) -> Expression {
152        if order == 1 {
153            return Self::simple_pole_residue(numerator, denominator, variable, pole);
154        }
155
156        // Res(f, a) = (1/(m-1)!) * lim_{z→a} d^(m-1)/dz^(m-1) [(z-a)^m * f(z)]
157        let z_minus_a = Expression::add(vec![
158            Expression::symbol(variable.clone()),
159            Expression::mul(vec![Expression::integer(-1), pole.clone()]),
160        ]);
161
162        let multiplied_expr = Expression::mul(vec![
163            Expression::pow(z_minus_a, Expression::integer(order as i64)),
164            Expression::mul(vec![
165                numerator.clone(),
166                Expression::pow(denominator.clone(), Expression::integer(-1)),
167            ]),
168        ]);
169
170        let derivative_expr = multiplied_expr.nth_derivative(variable.clone(), order - 1);
171
172        let factorial = Self::factorial(order - 1);
173        let limit_result = Expression::function(
174            "limit",
175            vec![
176                derivative_expr,
177                Expression::symbol(variable.clone()),
178                pole.clone(),
179            ],
180        );
181
182        Expression::mul(vec![
183            Expression::pow(factorial, Expression::integer(-1)),
184            limit_result,
185        ])
186        .simplify()
187    }
188
189    /// Compute factorial
190    ///
191    /// # Arguments
192    ///
193    /// * `n` - The number to compute factorial of
194    ///
195    /// # Returns
196    ///
197    /// Expression representing n!
198    pub fn factorial(n: u32) -> Expression {
199        let result = match n {
200            0 | 1 => 1,
201            _ => (2..=n as i64).product(),
202        };
203        Expression::integer(result)
204    }
205}
206
207impl ResidueCalculus for Expression {
208    fn residue(&self, variable: &Symbol, pole: &Expression) -> Expression {
209        // Check if this is a rational function
210        if let Expression::Mul(factors) = self {
211            if factors.len() == 2 {
212                if let Expression::Pow(denom, exp) = &factors[1] {
213                    if **exp == Expression::integer(-1) {
214                        // This is numerator/denominator form
215                        let numerator = &factors[0];
216                        let denominator = denom;
217
218                        // Determine pole order
219                        let order = self.pole_order(variable, pole);
220                        return ResidueMethods::higher_order_pole_residue(
221                            numerator,
222                            denominator,
223                            variable,
224                            pole,
225                            order,
226                        );
227                    }
228                }
229            }
230        }
231
232        // General case - use Laurent series
233        Expression::function(
234            "residue",
235            vec![
236                self.clone(),
237                Expression::symbol(variable.clone()),
238                pole.clone(),
239            ],
240        )
241    }
242
243    fn find_poles(&self, variable: &Symbol) -> Vec<Expression> {
244        // Find poles of the expression
245        match self {
246            // Rational functions: poles where denominator = 0
247            Expression::Mul(factors) if factors.len() == 2 => {
248                if let Expression::Pow(denom, exp) = &factors[1] {
249                    if **exp == Expression::integer(-1) {
250                        let numerator = &factors[0];
251                        return find_rational_poles(numerator, denom, variable);
252                    }
253                }
254                vec![]
255            }
256            // Direct reciprocal: 1/f(x)
257            Expression::Pow(base, exp) => {
258                if let Expression::Number(crate::core::Number::Integer(n)) = exp.as_ref() {
259                    if *n == -1 {
260                        return find_rational_poles(&Expression::integer(1), base, variable);
261                    }
262                }
263                vec![]
264            }
265            // Transcendental functions with known poles
266            Expression::Function { name, args } => find_transcendental_poles(name, args, variable),
267            _ => vec![],
268        }
269    }
270
271    fn contour_integral(&self, variable: &Symbol) -> Expression {
272        let poles = self.find_poles(variable);
273        if poles.is_empty() {
274            return Expression::integer(0);
275        }
276
277        // Residue theorem: ∮f(z)dz = 2πi * Σ Res(f, poles inside contour)
278        let residue_sum = if poles.len() == 1 {
279            self.residue(variable, &poles[0])
280        } else {
281            let residues: Vec<Expression> = poles
282                .iter()
283                .map(|pole| self.residue(variable, pole))
284                .collect();
285            Expression::add(residues)
286        };
287
288        Expression::mul(vec![
289            Expression::integer(2),
290            Expression::pi(),
291            Expression::i(),
292            residue_sum,
293        ])
294        .simplify()
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::expr;
302    use crate::symbol;
303
304    #[test]
305    fn test_simple_pole_residue() {
306        let z = symbol!(z);
307        let numerator = Expression::integer(1);
308        let denominator =
309            Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-1)]);
310        let pole = Expression::integer(1);
311
312        let result = ResidueMethods::simple_pole_residue(&numerator, &denominator, &z, &pole);
313
314        // Residue of 1/(z-1) at z=1 should be 1
315        assert!(matches!(result, Expression::Function { name, .. } if name.as_ref() == "limit"));
316    }
317
318    #[test]
319    fn test_factorial() {
320        assert_eq!(ResidueMethods::factorial(0), Expression::integer(1));
321        assert_eq!(ResidueMethods::factorial(1), Expression::integer(1));
322        assert_eq!(ResidueMethods::factorial(4), Expression::integer(24));
323        assert_eq!(ResidueMethods::factorial(5), Expression::integer(120));
324    }
325
326    #[test]
327    fn test_pole_order() {
328        let z = symbol!(z);
329        let expr = Expression::mul(vec![
330            Expression::integer(1),
331            Expression::pow(
332                Expression::pow(
333                    Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-1)]),
334                    Expression::integer(2),
335                ),
336                Expression::integer(-1),
337            ),
338        ]);
339        let pole = Expression::integer(1);
340
341        let order = expr.pole_order(&z, &pole);
342        assert!(order >= 1, "Pole order should be at least 1, got {}", order);
343    }
344
345    #[test]
346    fn test_is_analytic() {
347        let z = symbol!(z);
348        let polynomial = Expression::pow(Expression::symbol(z.clone()), Expression::integer(2));
349        let point = Expression::integer(1);
350
351        assert!(polynomial.is_analytic_at(&z, &point));
352    }
353
354    #[test]
355    fn test_pole_order_simple_pole() {
356        let z = symbol!(z);
357        // 1/(z-2) has a simple pole (order 1) at z=2
358        let expr = Expression::mul(vec![
359            Expression::integer(1),
360            Expression::pow(
361                Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-2)]),
362                Expression::integer(-1),
363            ),
364        ]);
365        let pole = Expression::integer(2);
366
367        let order = expr.pole_order(&z, &pole);
368        assert_eq!(order, 1, "1/(z-2) should have pole of order 1 at z=2");
369    }
370
371    #[test]
372    fn test_pole_order_double_pole() {
373        let z = symbol!(z);
374        // 1/(z-3)^2 has a double pole (order 2) at z=3
375        let expr = Expression::pow(
376            Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-3)]),
377            Expression::integer(-2),
378        );
379        let pole = Expression::integer(3);
380
381        let order = expr.pole_order(&z, &pole);
382        assert_eq!(order, 2, "1/(z-3)^2 should have pole of order 2 at z=3");
383    }
384
385    #[test]
386    fn test_pole_order_triple_pole() {
387        let z = symbol!(z);
388        // 1/(z-1)^3 has a triple pole (order 3) at z=1
389        let expr = Expression::pow(
390            Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-1)]),
391            Expression::integer(-3),
392        );
393        let pole = Expression::integer(1);
394
395        let order = expr.pole_order(&z, &pole);
396        assert_eq!(order, 3, "1/(z-1)^3 should have pole of order 3 at z=1");
397    }
398
399    #[test]
400    fn test_pole_order_no_pole() {
401        let z = symbol!(z);
402        // 1/(z-2) evaluated at z=5 should have no pole (order 0)
403        let expr = Expression::mul(vec![
404            Expression::integer(1),
405            Expression::pow(
406                Expression::add(vec![Expression::symbol(z.clone()), Expression::integer(-2)]),
407                Expression::integer(-1),
408            ),
409        ]);
410        let point = Expression::integer(5);
411
412        let order = expr.pole_order(&z, &point);
413        assert_eq!(
414            order, 0,
415            "1/(z-2) should have no pole at z=5 (denominator is non-zero)"
416        );
417    }
418
419    #[test]
420    fn test_pole_order_polynomial_no_pole() {
421        let z = symbol!(z);
422        // z^2 is a polynomial, no poles anywhere
423        let expr = Expression::pow(Expression::symbol(z.clone()), Expression::integer(2));
424        let point = Expression::integer(0);
425
426        let order = expr.pole_order(&z, &point);
427        assert_eq!(order, 0, "Polynomial z^2 should have no poles");
428    }
429
430    #[test]
431    fn test_pole_order_at_origin() {
432        let z = symbol!(z);
433        // 1/z has a simple pole at z=0
434        let expr = Expression::pow(Expression::symbol(z.clone()), Expression::integer(-1));
435        let pole = Expression::integer(0);
436
437        let order = expr.pole_order(&z, &pole);
438        assert_eq!(order, 1, "1/z should have pole of order 1 at z=0");
439    }
440
441    #[test]
442    fn test_pole_order_at_origin_higher() {
443        let z = symbol!(z);
444        // 1/z^4 has a pole of order 4 at z=0
445        let expr = Expression::pow(Expression::symbol(z.clone()), Expression::integer(-4));
446        let pole = Expression::integer(0);
447
448        let order = expr.pole_order(&z, &pole);
449        assert_eq!(order, 4, "1/z^4 should have pole of order 4 at z=0");
450    }
451
452    #[test]
453    fn test_classify_singularity_pole() {
454        let z = symbol!(z);
455        // 1/(z-1)^2 has a pole of order 2 at z=1
456        let expr = expr!((z - 1) ^ (-2));
457        let point = expr!(1);
458
459        let classification = classify_singularity(&expr, &z, &point);
460        assert_eq!(classification, SingularityType::Pole(2));
461    }
462
463    #[test]
464    fn test_find_poles_transcendental_tan() {
465        let x = symbol!(x);
466        let expr = Expression::function("tan", vec![Expression::symbol(x.clone())]);
467
468        let poles = expr.find_poles(&x);
469        assert!(!poles.is_empty(), "tan(x) should have poles");
470    }
471
472    #[test]
473    fn test_find_poles_transcendental_cot() {
474        let x = symbol!(x);
475        let expr = Expression::function("cot", vec![Expression::symbol(x.clone())]);
476
477        let poles = expr.find_poles(&x);
478        assert!(!poles.is_empty(), "cot(x) should have poles");
479        assert_eq!(poles[0], expr!(0));
480    }
481}