Skip to main content

rssn/numerical/
complex_analysis.rs

1//! # Numerical Complex Analysis
2//!
3//! This module provides numerical tools for complex analysis.
4//! It includes functions for evaluating symbolic expressions to complex numbers,
5//! which is fundamental for numerical computations involving complex functions.
6
7use std::collections::HashMap;
8
9use num_complex::Complex;
10use num_traits::ToPrimitive;
11use num_traits::Zero;
12use serde::Deserialize;
13use serde::Serialize;
14
15use crate::symbolic::core::Expr;
16
17/// # Numerical Contour Integration (Simpson's Rule)
18///
19/// This function computes the contour integral of a complex function `f` along a given path.
20/// It uses Simpson's rule for improved accuracy over the trapezoidal rule.
21///
22/// ## Arguments
23/// * `f` - The complex function to integrate, represented as a closure.
24/// * `path` - A slice of `Complex<f64>` points defining the contour.
25///
26/// ## Returns
27/// The numerical result of the contour integral.
28pub fn contour_integral<F>(
29    f: F,
30    path: &[Complex<f64>],
31) -> Complex<f64>
32where
33    F: Fn(Complex<f64>) -> Complex<f64>,
34{
35    let mut integral = Complex::zero();
36
37    for i in 0..path.len() - 1 {
38        let z1 = path[i];
39
40        let z2 = path[i + 1];
41
42        let mid = (z1 + z2) / 2.0;
43
44        let dz = z2 - z1;
45
46        integral += (f(z1) + 4.0 * f(mid) + f(z2)) / 6.0 * dz;
47    }
48
49    integral
50}
51
52/// # Residue Calculation
53///
54/// This function calculates the residue of a complex function `f` at a point `z0`.
55/// It uses a small circular contour around `z0` to compute the residue via Cauchy's Residue Theorem.
56///
57/// ## Arguments
58/// * `f` - The complex function, represented as a closure.
59/// * `z0` - The point at which to calculate the residue.
60/// * `radius` - The radius of the circular contour to use for the calculation.
61/// * `n_points` - The number of points to use for the contour integration.
62///
63/// ## Returns
64/// The residue of the function at `z0`.
65pub fn residue<F>(
66    f: F,
67    z0: Complex<f64>,
68    radius: f64,
69    n_points: usize,
70) -> Complex<f64>
71where
72    F: Fn(Complex<f64>) -> Complex<f64>,
73{
74    let mut path = Vec::with_capacity(n_points + 1);
75
76    for i in 0..=n_points {
77        let angle = 2.0 * std::f64::consts::PI * (i as f64) / (n_points as f64);
78
79        let point = z0 + radius * Complex::new(angle.cos(), angle.sin());
80
81        path.push(point);
82    }
83
84    let integral = contour_integral(f, &path);
85
86    integral / (2.0 * std::f64::consts::PI * Complex::new(0.0, 1.0))
87}
88
89/// # Cauchy's Argument Principle
90///
91/// Calculates the number of zeros minus the number of poles (N - P) of a function `f`
92/// inside a given contour. It does so by computing the winding number of f(z) around the origin.
93///
94/// ## Arguments
95/// * `f` - The complex function.
96/// * `contour` - A closed path in the complex plane.
97///
98/// ## Returns
99/// The difference between the number of zeros and poles, which should be an integer.
100pub fn count_zeros_poles<F>(
101    f: F,
102    contour: &[Complex<f64>],
103) -> Complex<f64>
104where
105    F: Fn(Complex<f64>) -> Complex<f64>,
106{
107    let integral = contour_integral(|z| complex_derivative(&f, z) / f(z), contour);
108
109    integral / (2.0 * std::f64::consts::PI * Complex::new(0.0, 1.0))
110}
111
112/// # Numerical Differentiation
113///
114/// Computes the derivative of a complex function `f` at a point `z` using the central difference formula.
115///
116/// ## Arguments
117/// * `f` - The complex function.
118/// * `z` - The point at which to compute the derivative.
119///
120/// ## Returns
121/// The numerical derivative of `f` at `z`.
122pub fn complex_derivative<F>(
123    f: &F,
124    z: Complex<f64>,
125) -> Complex<f64>
126where
127    F: Fn(Complex<f64>) -> Complex<f64>,
128{
129    let h = 1e-6;
130
131    let h_complex = Complex::new(h, h);
132
133    (f(z + h_complex) - f(z - h_complex)) / (2.0 * h_complex)
134}
135
136/// Represents a numerical Möbius transformation: f(z) = (az + b) / (cz + d)
137#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
138pub struct MobiusTransformation {
139    /// Coefficient a of the Möbius transformation.
140    pub a: Complex<f64>,
141    /// Coefficient b of the Möbius transformation.
142    pub b: Complex<f64>,
143    /// Coefficient c of the Möbius transformation.
144    pub c: Complex<f64>,
145    /// Coefficient d of the Möbius transformation.
146    pub d: Complex<f64>,
147}
148
149impl MobiusTransformation {
150    /// Creates a new Möbius transformation.
151    #[must_use]
152    pub const fn new(
153        a: Complex<f64>,
154        b: Complex<f64>,
155        c: Complex<f64>,
156        d: Complex<f64>,
157    ) -> Self {
158        Self { a, b, c, d }
159    }
160
161    /// Applies the transformation to a point z.
162    #[must_use]
163    pub fn apply(
164        &self,
165        z: Complex<f64>,
166    ) -> Complex<f64> {
167        (self.a * z + self.b) / (self.c * z + self.d)
168    }
169
170    /// Composes two Möbius transformations.
171    #[must_use]
172    #[allow(clippy::suspicious_operation_groupings)]
173    pub fn compose(
174        &self,
175        other: &Self,
176    ) -> Self {
177        Self {
178            a: self.a * other.a + self.b * other.c,
179            b: self.a * other.b + self.b * other.d,
180            c: self.c * other.a + self.d * other.c,
181            d: self.c * other.b + self.d * other.d,
182        }
183    }
184
185    /// Computes the inverse transformation.
186    #[must_use]
187    pub fn inverse(&self) -> Self {
188        Self {
189            a: self.d,
190            b: -self.b,
191            c: -self.c,
192            d: self.a,
193        }
194    }
195}
196
197/// Performs numerical contour integration of a symbolic expression.
198///
199/// * `path` - A slice of complex points defining the contour.
200///
201/// # Errors
202///
203/// Returns an error if the expression evaluation fails.
204pub fn contour_integral_expr(
205    expr: &Expr,
206    var: &str,
207    path: &[Complex<f64>],
208) -> Result<Complex<f64>, String> {
209    let f = |z: Complex<f64>| {
210        let mut vars = HashMap::new();
211
212        vars.insert(var.to_string(), z);
213
214        eval_complex_expr(expr, &vars).unwrap_or_else(|_| Complex::zero())
215    };
216
217    Ok(contour_integral(f, path))
218}
219
220/// Calculates the residue of a symbolic expression at a point.
221///
222/// # Arguments
223/// * `expr` - The symbolic expression.
224/// * `n_points` - Number of points for integration.
225///
226/// # Errors
227///
228/// Returns an error if the residue calculation fails (e.g., expression evaluation error).
229pub fn residue_expr(
230    expr: &Expr,
231    var: &str,
232    z0: Complex<f64>,
233    radius: f64,
234    n_points: usize,
235) -> Result<Complex<f64>, String> {
236    let f = |z: Complex<f64>| {
237        let mut vars = HashMap::new();
238
239        vars.insert(var.to_string(), z);
240
241        eval_complex_expr(expr, &vars).unwrap_or_else(|_| Complex::zero())
242    };
243
244    Ok(residue(f, z0, radius, n_points))
245}
246
247/// Evaluates a symbolic expression to a numerical `Complex<f64>` value.
248///
249/// This function recursively traverses the expression tree and computes the complex numerical value.
250/// It handles basic arithmetic, trigonometric, exponential, and logarithmic functions for complex numbers.
251///
252/// # Arguments
253/// * `expr` - The expression to evaluate.
254/// * `vars` - A `HashMap` containing the numerical `Complex<f64>` values for the variables in the expression.
255///
256/// # Returns
257/// A `Result` containing the complex numerical value if the evaluation is successful, otherwise an error string.
258///
259/// # Errors
260///
261/// Returns an error if:
262/// - A variable in the expression is not provided in the `vars` map.
263/// - An unsupported operation is encountered for complex numbers.
264/// - Division by zero or other mathematical errors occur.
265pub fn eval_complex_expr<S: ::std::hash::BuildHasher>(
266    expr: &Expr,
267    vars: &HashMap<String, Complex<f64>, S>,
268) -> Result<Complex<f64>, String> {
269    match expr {
270        | Expr::Dag(node) => {
271            let inner = node.to_expr()?;
272
273            eval_complex_expr(&inner, vars)
274        },
275        | Expr::Constant(c) => Ok(Complex::new(*c, 0.0)),
276        | Expr::BigInt(i) => {
277            Ok(Complex::new(
278                i.to_f64().ok_or(
279                    "f64 conversion \
280                     failed",
281                )?,
282                0.0,
283            ))
284        },
285        | Expr::Variable(v) => {
286            vars.get(v).copied().ok_or_else(|| {
287                format!(
288                    "Variable '{v}' \
289                     not found"
290                )
291            })
292        },
293        | Expr::Complex(re, im) => {
294            let re_val = eval_complex_expr(re, vars)?.re;
295
296            let im_val = eval_complex_expr(im, vars)?.re;
297
298            Ok(Complex::new(re_val, im_val))
299        },
300        | Expr::Add(a, b) => Ok(eval_complex_expr(a, vars)? + eval_complex_expr(b, vars)?),
301        | Expr::Sub(a, b) => Ok(eval_complex_expr(a, vars)? - eval_complex_expr(b, vars)?),
302        | Expr::Mul(a, b) => Ok(eval_complex_expr(a, vars)? * eval_complex_expr(b, vars)?),
303        | Expr::Div(a, b) => Ok(eval_complex_expr(a, vars)? / eval_complex_expr(b, vars)?),
304        | Expr::Power(b, e) => Ok(eval_complex_expr(b, vars)?.powc(eval_complex_expr(e, vars)?)),
305        | Expr::Neg(a) => Ok(-eval_complex_expr(a, vars)?),
306        | Expr::Sqrt(a) => Ok(eval_complex_expr(a, vars)?.sqrt()),
307        | Expr::Abs(a) => Ok(Complex::new(eval_complex_expr(a, vars)?.norm(), 0.0)),
308        | Expr::Sin(a) => Ok(eval_complex_expr(a, vars)?.sin()),
309        | Expr::Cos(a) => Ok(eval_complex_expr(a, vars)?.cos()),
310        | Expr::Tan(a) => Ok(eval_complex_expr(a, vars)?.tan()),
311        | Expr::Log(a) => Ok(eval_complex_expr(a, vars)?.ln()),
312        | Expr::Exp(a) => Ok(eval_complex_expr(a, vars)?.exp()),
313        | Expr::Pi => Ok(Complex::new(std::f64::consts::PI, 0.0)),
314        | Expr::E => Ok(Complex::new(std::f64::consts::E, 0.0)),
315        | Expr::Atan2(y, x) => {
316            let y_val = eval_complex_expr(y, vars)?.re;
317
318            let x_val = eval_complex_expr(x, vars)?.re;
319
320            Ok(Complex::new(y_val.atan2(x_val), 0.0))
321        },
322        | _ => {
323            Err(format!(
324                "Numerical complex \
325                 evaluation for \
326                 expression {expr:?} \
327                 is not implemented"
328            ))
329        },
330    }
331}