1use 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
17pub 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
52pub 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
89pub 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
112pub 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#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
138pub struct MobiusTransformation {
139 pub a: Complex<f64>,
141 pub b: Complex<f64>,
143 pub c: Complex<f64>,
145 pub d: Complex<f64>,
147}
148
149impl MobiusTransformation {
150 #[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 #[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 #[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 #[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
197pub 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
220pub 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
247pub 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}