Skip to main content

quad_rs/contour/
piece.rs

1//! Parametric contour pieces.
2//!
3//! This module defines the primitive pieces from which integration contours are
4//! built.
5//!
6//! A contour piece is a parametric map from the reference parameter interval
7//! `t ∈ [0, 1]` to the physical input domain. During quadrature, the integrator
8//! evaluates the integrand at `piece.point(t)` and multiplies by
9//! `piece.derivative(t)` so that curved and non-uniformly parameterized paths
10//! are handled correctly.
11//!
12//! For a contour piece `z(t)`, the integral is evaluated as:
13//!
14//! ```text
15//! ∫ f(z) dz = ∫₀¹ f(z(t)) z'(t) dt
16//! ```
17//!
18//! Line segments are generic over real or complex input types. Circular arcs
19//! are complex-valued pieces.
20
21use nalgebra::ComplexField;
22use num_traits::Float;
23use std::ops::Range;
24
25use crate::integrable::ComplexScalar;
26
27/// A single parametric piece of an integration contour.
28///
29/// A `ContourPiece` maps the reference interval `t ∈ [0, 1]` to the physical
30/// integration domain.
31///
32/// The adaptive integrator uses this trait to evaluate quadrature nodes,
33/// compute geometric weights, and subdivide pieces during refinement.
34///
35/// # Required behaviour
36///
37/// Implementations should satisfy:
38///
39/// ```text
40/// point(t)      = physical point at parameter t
41/// derivative(t) = d(point)/dt
42/// ```
43///
44/// for `t ∈ [0, 1]`.
45pub(crate) trait ContourPiece: Clone {
46    /// Physical input type of the integrand.
47    type Input: Clone + std::fmt::Debug;
48
49    /// Underlying real floating-point type.
50    type Float;
51
52    /// Returns the physical contour point at parameter `t`.
53    ///
54    /// The parameter `t` is expected to lie in `[0, 1]`.
55    fn point(&self, t: Self::Float) -> Self::Input;
56
57    /// Returns the derivative of the contour map at parameter `t`.
58    ///
59    /// This is the geometric Jacobian `dz/dt`. The quadrature weight at each node
60    /// is multiplied by this value.
61    fn derivative(&self, t: Self::Float) -> Self::Input;
62
63    /// Returns a characteristic physical size for this piece.
64    ///
65    /// This value is used to decide whether a piece is too small to subdivide
66    /// further. For a line segment this is its length. For a curved piece this may
67    /// be an arc length or another conservative length scale.
68    fn length_scale(&self) -> Self::Float;
69
70    /// Returns `true` if this piece has zero geometric extent.
71    fn is_degenerate(&self) -> bool;
72
73    /// Splits this piece into two subpieces.
74    ///
75    /// The two returned pieces should cover the same path as the original piece,
76    /// preserving orientation.
77    fn split(&self) -> [Self; 2]
78    where
79        Self: Sized;
80}
81
82pub trait SplittableContourPiece: ContourPiece {
83    fn locate_point(&self, point: Self::Input, tolerance: Self::Float) -> Option<Self::Float>;
84    fn split_at(&self, t: Self::Float) -> [Self; 2];
85}
86
87/// Built-in complex contour segment.
88///
89/// This enum allows a single contour to contain heterogeneous built-in piece
90/// types, such as straight line segments and circular arcs, while still
91/// presenting one concrete type to the integrator.
92#[derive(Clone, Debug)]
93pub enum ContourSegment<F>
94where
95    F: ComplexScalar,
96{
97    /// Straight line segment
98    Line(LineSegment<<F as ComplexScalar>::Complex>),
99    /// Circular arc
100    CircularArc(CircularArc<F>),
101}
102
103impl<F: ComplexScalar> ContourSegment<F> {
104    pub fn start(&self) -> F::Complex {
105        match self {
106            Self::Line(line) => line.start(),
107            Self::CircularArc(arc) => arc.point(F::zero()),
108        }
109    }
110
111    pub fn end(&self) -> F::Complex {
112        match self {
113            Self::Line(line) => line.end(),
114            Self::CircularArc(arc) => arc.point(F::one()),
115        }
116    }
117
118    pub fn reversed(self) -> Self {
119        match self {
120            Self::Line(line) => Self::Line(line.reversed()),
121            Self::CircularArc(arc) => Self::CircularArc(arc.reversed()),
122        }
123    }
124}
125
126impl<F> ContourPiece for ContourSegment<F>
127where
128    F: ComplexScalar,
129{
130    type Input = F::Complex;
131    type Float = F;
132
133    fn point(&self, t: F) -> Self::Input {
134        match self {
135            Self::Line(piece) => piece.point(t),
136            Self::CircularArc(piece) => piece.point(t),
137        }
138    }
139
140    fn derivative(&self, t: F) -> Self::Input {
141        match self {
142            Self::Line(piece) => piece.derivative(t),
143            Self::CircularArc(piece) => piece.derivative(t),
144        }
145    }
146
147    fn length_scale(&self) -> F {
148        match self {
149            Self::Line(piece) => piece.length_scale(),
150            Self::CircularArc(piece) => piece.length_scale(),
151        }
152    }
153
154    fn is_degenerate(&self) -> bool {
155        match self {
156            Self::Line(piece) => piece.is_degenerate(),
157            Self::CircularArc(piece) => piece.is_degenerate(),
158        }
159    }
160
161    fn split(&self) -> [Self; 2] {
162        match self {
163            Self::Line(piece) => {
164                let [a, b] = piece.split();
165                [Self::Line(a), Self::Line(b)]
166            }
167            Self::CircularArc(piece) => {
168                let [a, b] = piece.split();
169                [Self::CircularArc(a), Self::CircularArc(b)]
170            }
171        }
172    }
173}
174
175/// Straight contour piece between two points.
176///
177/// `LineSegment` is generic over the input type and can represent both real
178/// intervals and complex line segments.
179#[derive(Debug, Clone, Copy)]
180pub struct LineSegment<I> {
181    start: I,
182    end: I,
183}
184
185impl<I> From<Range<I>> for LineSegment<I> {
186    fn from(range: Range<I>) -> Self {
187        Self {
188            start: range.start,
189            end: range.end,
190        }
191    }
192}
193
194impl<I> LineSegment<I> {
195    /// Creates a new line segment from `start` to `end`.
196    pub fn new(start: I, end: I) -> Self {
197        Self::from(start..end)
198    }
199
200    pub fn start(&self) -> I
201    where
202        I: Copy,
203    {
204        self.start
205    }
206
207    pub fn end(&self) -> I
208    where
209        I: Copy,
210    {
211        self.end
212    }
213
214    pub fn reversed(self) -> Self
215    where
216        I: Copy,
217    {
218        Self::new(self.end, self.start)
219    }
220}
221
222impl<I, F> ContourPiece for LineSegment<I>
223where
224    I: ComplexField<RealField = F> + Copy,
225    F: Float,
226{
227    type Float = F;
228    type Input = I;
229
230    fn point(&self, t: Self::Float) -> Self::Input {
231        self.start + (self.end - self.start).scale(t)
232    }
233
234    fn derivative(&self, _t: Self::Float) -> Self::Input {
235        self.end - self.start
236    }
237
238    fn length_scale(&self) -> Self::Float {
239        (self.end - self.start).modulus()
240    }
241
242    fn is_degenerate(&self) -> bool {
243        self.length_scale() == F::zero()
244    }
245
246    fn split(&self) -> [Self; 2] {
247        let half = F::one() / (F::one() + F::one());
248        let mid = self.point(half);
249        [
250            Self {
251                start: self.start,
252                end: mid,
253            },
254            Self {
255                start: mid,
256                end: self.end,
257            },
258        ]
259    }
260}
261
262/// Circular arc in the complex plane.
263///
264/// The arc is parameterized by
265///
266/// ```text
267/// z(t) = center + radius * exp(i * theta(t))
268/// theta(t) = theta0 + (theta1 - theta0) * t
269/// ```
270///
271/// for `t ∈ [0, 1]`.
272///
273/// The orientation is determined by the sign of `theta1 - theta0`.
274#[derive(Clone, Copy, Debug)]
275pub struct CircularArc<F: ComplexScalar> {
276    center: F::Complex,
277    radius: F,
278    theta0: F,
279    theta1: F,
280}
281
282impl<F: ComplexScalar> CircularArc<F> {
283    /// Creates a circular arc.
284    ///
285    /// - `center`: centre of the circle,
286    /// - `radius`: circle radius,
287    /// - `theta0`: starting angle in radians,
288    /// - `theta1`: ending angle in radians.
289    pub fn new(center: F::Complex, radius: F, theta0: F, theta1: F) -> Self {
290        Self {
291            center,
292            radius,
293            theta0,
294            theta1,
295        }
296    }
297
298    /// Returns the physical angle corresponding to parameter `t`.
299    fn theta(&self, t: F) -> F
300    where
301        F: Float,
302    {
303        self.theta0 + (self.theta1 - self.theta0) * t
304    }
305
306    pub fn center(&self) -> F::Complex
307    where
308        F::Complex: Copy,
309    {
310        self.center
311    }
312
313    pub fn radius(&self) -> F
314    where
315        F: Copy,
316    {
317        self.radius
318    }
319
320    pub fn reversed(self) -> Self
321    where
322        F: Copy,
323    {
324        Self::new(self.center, self.radius, self.theta1, self.theta0)
325    }
326}
327
328impl<F> ContourPiece for CircularArc<F>
329where
330    F: ComplexScalar,
331{
332    type Input = F::Complex;
333    type Float = F;
334
335    fn point(&self, t: Self::Float) -> Self::Input {
336        let theta = self.theta(t);
337
338        self.center + F::complex(self.radius * theta.cos(), self.radius * theta.sin())
339    }
340
341    fn derivative(&self, t: Self::Float) -> Self::Input {
342        let theta = self.theta(t);
343        let dtheta_dt = self.theta1 - self.theta0;
344
345        F::complex(
346            -self.radius * theta.sin() * dtheta_dt,
347            self.radius * theta.cos() * dtheta_dt,
348        )
349    }
350
351    fn length_scale(&self) -> Self::Float {
352        self.radius * (self.theta1 - self.theta0).abs()
353    }
354
355    fn is_degenerate(&self) -> bool {
356        self.radius == F::zero() || self.theta0 == self.theta1
357    }
358
359    fn split(&self) -> [Self; 2] {
360        let mid = (self.theta0 + self.theta1) / (F::one() + F::one());
361
362        [
363            Self::new(self.center, self.radius, self.theta0, mid),
364            Self::new(self.center, self.radius, mid, self.theta1),
365        ]
366    }
367}
368
369pub enum InfiniteInterval<F> {
370    Whole,
371    From(F),
372    To(F),
373}