use approx::relative_eq;
use nalgebra::ComplexField;
use num_traits::FromPrimitive;
#[derive(Clone, Debug)]
pub struct Contour<I> {
pub range: Vec<std::ops::Range<I>>,
}
#[derive(Copy, Clone, Debug)]
pub enum Direction {
Clockwise,
CounterClockwise,
}
impl<I> Contour<I> {
pub fn reversed(self) -> Self {
Self {
range: self.range.into_iter().rev().collect(),
}
}
}
impl<I> Contour<I>
where
I: ComplexField + FromPrimitive + Copy,
<I as ComplexField>::RealField: Copy,
{
pub fn generate_rectangular(
x_range: &std::ops::Range<I::RealField>,
y_range: &std::ops::Range<I::RealField>,
direction: Direction,
) -> Self {
let mut range = Self::generate_rectangular_internal(x_range, y_range);
match direction {
Direction::Clockwise => Self { range },
Direction::CounterClockwise => {
range.reverse();
Self { range }
}
}
}
pub fn generate_rectangular_internal(
x_range: &std::ops::Range<I::RealField>,
y_range: &std::ops::Range<I::RealField>,
) -> Vec<std::ops::Range<I>> {
let i = -<I as ComplexField>::try_sqrt(-I::one()).unwrap();
let path = [
I::from_real(x_range.start) + i * I::from_real(y_range.start),
I::from_real(x_range.end) + i * I::from_real(y_range.start),
I::from_real(x_range.end) + i * I::from_real(y_range.end),
I::from_real(x_range.start) + i * I::from_real(y_range.end),
I::from_real(x_range.start) + i * I::from_real(y_range.start),
];
let connected_range = path
.windows(2)
.map(|points| std::ops::Range {
start: points[0],
end: points[1],
})
.collect::<Vec<_>>();
connected_range
}
pub fn deform_outward_around_singularity(&mut self, singularity: I) {
let delta = <I as ComplexField>::RealField::from_f64(1e-5).unwrap();
let mut x_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
start: self.range[0].start.real(),
end: self.range[0].end.real(),
};
let mut y_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
start: self.range[1].start.imaginary(),
end: self.range[1].end.imaginary(),
};
if relative_eq!(singularity.real(), x_range.start) {
x_range.start -= delta;
} else if relative_eq!(singularity.real(), x_range.end) {
x_range.end += delta;
} else if relative_eq!(singularity.imaginary(), y_range.start) {
y_range.start -= delta;
} else if relative_eq!(singularity.imaginary(), y_range.end) {
y_range.end += delta;
}
self.range = Self::generate_rectangular_internal(&x_range, &y_range);
}
pub fn deform_inward_around_singularity(&mut self, singularity: I) {
let delta = <I as ComplexField>::RealField::from_f64(-1e-5).unwrap();
let mut x_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
start: self.range[0].start.real(),
end: self.range[0].end.real(),
};
let mut y_range: std::ops::Range<<I as ComplexField>::RealField> = std::ops::Range {
start: self.range[1].start.imaginary(),
end: self.range[1].end.imaginary(),
};
if relative_eq!(singularity.real(), x_range.start) {
x_range.start -= delta;
} else if relative_eq!(singularity.real(), x_range.end) {
x_range.end += delta;
} else if relative_eq!(singularity.imaginary(), y_range.start) {
y_range.start -= delta;
} else if relative_eq!(singularity.imaginary(), y_range.end) {
y_range.end += delta;
}
self.range = Self::generate_rectangular_internal(&x_range, &y_range);
}
}
pub fn split_range_once_around_singularity<I>(
range: std::ops::Range<I>,
singularity: I,
) -> [std::ops::Range<I>; 2]
where
I: ComplexField + Copy,
{
let ranges = split_range_around_singularities(range, vec![singularity]);
[ranges[0].clone(), ranges[1].clone()] }
pub fn split_range_around_singularities<I>(
range: std::ops::Range<I>,
mut singularities: Vec<I>,
) -> Vec<std::ops::Range<I>>
where
I: ComplexField + Copy,
{
if singularities.is_empty() {
return vec![range];
}
singularities.sort_by(|a, b| a.real().partial_cmp(&b.real()).unwrap());
let mut points = vec![range.start];
for singularity in singularities {
points.push(singularity);
}
points.push(range.end);
let epsilon = I::from_f64(1e-8).unwrap();
let stop = points.len() - 2;
let mut new_range = vec![];
for (idx, window) in points.windows(2).enumerate() {
let left = if idx == 0 {
window[0]
} else {
window[0] + epsilon
};
let right = if idx == stop {
window[1]
} else {
window[1] - epsilon
};
new_range.push(left..right);
}
new_range
}
#[cfg(test)]
mod test {
use super::{Contour, Direction};
use num_complex::Complex;
use std::ops::Range;
#[test]
fn simple_pole_contour_integral_evaluates_successfully() {
let x_range = Range {
start: -0.5,
end: 0.5,
};
let y_range = Range {
start: -0.5,
end: 0.5,
};
let _contour: Contour<Complex<f64>> =
Contour::generate_rectangular(&x_range, &y_range, Direction::Clockwise);
}
}