1use approx::relative_eq;
2use nalgebra::ComplexField;
3
4use num_traits::FromPrimitive;
5
6#[derive(Clone, Debug)]
7pub struct Contour<I> {
10 pub range: Vec<std::ops::Range<I>>,
12}
13
14#[derive(Copy, Clone, Debug)]
16pub enum Direction {
17 Clockwise,
19 CounterClockwise,
21}
22
23impl<I> Contour<I> {
24 pub fn reversed(self) -> Self {
25 Self {
26 range: self.range.into_iter().rev().collect(),
27 }
28 }
29}
30
31impl<I> Contour<I>
32where
33 I: ComplexField + FromPrimitive + Copy,
34 <I as ComplexField>::RealField: Copy,
35{
36 pub fn generate_rectangular(
38 x_range: &std::ops::Range<I::RealField>,
39 y_range: &std::ops::Range<I::RealField>,
40 direction: Direction,
41 ) -> Self {
42 let mut range = Self::generate_rectangular_internal(x_range, y_range);
43 match direction {
44 Direction::Clockwise => Self { range },
45 Direction::CounterClockwise => {
46 range.reverse();
47 Self { range }
48 }
49 }
50 }
51
52 pub fn generate_rectangular_internal(
54 x_range: &std::ops::Range<I::RealField>,
55 y_range: &std::ops::Range<I::RealField>,
56 ) -> Vec<std::ops::Range<I>> {
57 let i = -<I as ComplexField>::try_sqrt(-I::one()).unwrap();
59 let path = [
60 I::from_real(x_range.start) + i * I::from_real(y_range.start),
61 I::from_real(x_range.end) + i * I::from_real(y_range.start),
62 I::from_real(x_range.end) + i * I::from_real(y_range.end),
63 I::from_real(x_range.start) + i * I::from_real(y_range.end),
64 I::from_real(x_range.start) + i * I::from_real(y_range.start),
65 ];
66 let connected_range = path
67 .windows(2)
68 .map(|points| std::ops::Range {
69 start: points[0],
70 end: points[1],
71 })
72 .collect::<Vec<_>>();
73
74 connected_range
75 }
76
77 pub fn deform_outward_around_singularity(&mut self, singularity: I) {
79 let delta = <I as ComplexField>::RealField::from_f64(1e-5).unwrap();
80 let mut x_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
81 start: self.range[0].start.real(),
82 end: self.range[0].end.real(),
83 };
84 let mut y_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
85 start: self.range[1].start.imaginary(),
86 end: self.range[1].end.imaginary(),
87 };
88
89 if relative_eq!(singularity.real(), x_range.start) {
90 x_range.start -= delta;
91 } else if relative_eq!(singularity.real(), x_range.end) {
92 x_range.end += delta;
93 } else if relative_eq!(singularity.imaginary(), y_range.start) {
94 y_range.start -= delta;
95 } else if relative_eq!(singularity.imaginary(), y_range.end) {
96 y_range.end += delta;
97 }
98
99 self.range = Self::generate_rectangular_internal(&x_range, &y_range);
100 }
101
102 pub fn deform_inward_around_singularity(&mut self, singularity: I) {
104 let delta = <I as ComplexField>::RealField::from_f64(-1e-5).unwrap();
105 let mut x_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
106 start: self.range[0].start.real(),
107 end: self.range[0].end.real(),
108 };
109 let mut y_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
110 start: self.range[1].start.imaginary(),
111 end: self.range[1].end.imaginary(),
112 };
113
114 if relative_eq!(singularity.real(), x_range.start) {
115 x_range.start -= delta;
116 } else if relative_eq!(singularity.real(), x_range.end) {
117 x_range.end += delta;
118 } else if relative_eq!(singularity.imaginary(), y_range.start) {
119 y_range.start -= delta;
120 } else if relative_eq!(singularity.imaginary(), y_range.end) {
121 y_range.end += delta;
122 }
123
124 self.range = Self::generate_rectangular_internal(&x_range, &y_range);
125 }
126}
127
128pub fn split_range_once_around_singularity<I>(
130 range: std::ops::Range<I>,
131 singularity: I,
132) -> [std::ops::Range<I>; 2]
133where
134 I: ComplexField + Copy,
135{
136 let ranges = split_range_around_singularities(range, vec![singularity]);
137 [ranges[0].clone(), ranges[1].clone()] }
139
140pub fn split_range_around_singularities<I>(
142 range: std::ops::Range<I>,
143 mut singularities: Vec<I>,
144) -> Vec<std::ops::Range<I>>
145where
146 I: ComplexField + Copy,
147{
148 if singularities.is_empty() {
149 return vec![range];
150 }
151
152 singularities.sort_by(|a, b| a.real().partial_cmp(&b.real()).unwrap());
153
154 let mut points = vec![range.start];
155 for singularity in singularities {
156 points.push(singularity);
157 }
158 points.push(range.end);
159 let epsilon = I::from_f64(1e-8).unwrap();
160
161 let stop = points.len() - 2;
162 let mut new_range = vec![];
163 for (idx, window) in points.windows(2).enumerate() {
164 let left = if idx == 0 {
165 window[0]
166 } else {
167 window[0] + epsilon
168 };
169 let right = if idx == stop {
170 window[1]
171 } else {
172 window[1] - epsilon
173 };
174 new_range.push(left..right);
175 }
176 new_range
177}
178
179#[cfg(test)]
180mod test {
181 use super::{Contour, Direction};
182 use num_complex::Complex;
183 use std::ops::Range;
184
185 #[test]
186 fn simple_pole_contour_integral_evaluates_successfully() {
187 let x_range = Range {
188 start: -0.5,
189 end: 0.5,
190 };
191 let y_range = Range {
192 start: -0.5,
193 end: 0.5,
194 };
195
196 let _contour: Contour<Complex<f64>> =
197 Contour::generate_rectangular(&x_range, &y_range, Direction::Clockwise);
198 }
199}