Skip to main content

quad_rs/
lib.rs

1//! Adaptive real and complex numerical integration.
2//!
3//! `quad-rs` provides adaptive Gauss–Kronrod quadrature for real intervals,
4//! complex contours, and user-defined parameterised integration paths.
5//!
6//! The crate is designed around three core ideas:
7//!
8//! - integrands are ordinary Rust types implementing [`Integrable`],
9//! - integration domains are represented by finite contour pieces,
10//! - adaptive refinement is driven by local Gauss–Kronrod error estimates.
11//!
12//! # Features
13//!
14//! - Real-valued integration over finite intervals.
15//! - Complex contour integration.
16//! - Piecewise-linear contours.
17//! - Circular arcs and closed half-disk contours.
18//! - Local contour indentation around poles.
19//! - Scalar, complex, vector, matrix, and array-valued outputs via
20//!   [`IntegrationOutput`].
21//! - Optional storage of quadrature samples for diagnostics and plotting.
22//!
23//! # Real integration
24//!
25//! ```
26//! use quad_rs::{integrate_real, Integrable, IntegratorConfig};
27//!
28//! struct Gaussian;
29//!
30//! impl Integrable for Gaussian {
31//!     type Float = f64;
32//!     type Input = f64;
33//!     type Output = f64;
34//!
35//!     fn integrand(&self, x: &f64) -> f64 {
36//!         (-x * x).exp()
37//!     }
38//! }
39//!
40//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
41//! let result = integrate_real(
42//!     Gaussian,
43//!     vec![-4.0, 4.0],
44//!     IntegratorConfig::default(),
45//! )?;
46//!
47//! println!("integral = {}", result.integral);
48//! println!("error    = {}", result.error);
49//! # Ok(())
50//! # }
51//! ```
52//!
53//! # Complex contour integration
54//!
55//! ```
56//! use num_complex::Complex;
57//! use quad_rs::{integrate_complex, Contour, Integrable, IntegratorConfig};
58//!
59//! struct InverseZ;
60//!
61//! impl Integrable for InverseZ {
62//!     type Float = f64;
63//!     type Input = Complex<f64>;
64//!     type Output = Complex<f64>;
65//!
66//!     fn integrand(&self, z: &Complex<f64>) -> Complex<f64> {
67//!         Complex::new(1.0, 0.0) / *z
68//!     }
69//! }
70//!
71//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
72//! let contour = Contour::upper_half_disk_offset(1.0, 1e-5);
73//!
74//! let result = integrate_complex(
75//!     InverseZ,
76//!     contour,
77//!     IntegratorConfig::default(),
78//! )?;
79//!
80//! println!("integral = {}", result.integral);
81//! # Ok(())
82//! # }
83//! ```
84//!
85//! # Contour deformation
86//!
87//! Known poles can be avoided by locally replacing part of a line segment with
88//! a small circular indentation.
89//!
90//! ```
91//! use num_complex::Complex;
92//! use quad_rs::{Contour, IndentSide};
93//!
94//! let contour = Contour::real_axis(5.0)
95//!     .indent(
96//!         Complex::new(0.0, 0.0),
97//!         1e-3,
98//!         IndentSide::Left,
99//!         1e-10,
100//!     );
101//! ```
102//!
103//! This is useful for Cauchy principal values, Green's functions, residue
104//! calculations, and `i0⁺`/`i0⁻` prescriptions.
105//!
106//! # Configuration
107//!
108//! [`IntegratorConfig`] controls tolerances, quadrature order, error reduction,
109//! singularity handling, and whether quadrature samples are stored.
110//!
111//! ```
112//! use quad_rs::{ErrorNorm, IntegratorConfig};
113//!
114//! let config = IntegratorConfig::default()
115//!     .with_absolute_tolerance(1e-10)
116//!     .with_relative_tolerance(1e-10)
117//!     .with_error_norm(ErrorNorm::Max)
118//!     .store_segment_data();
119//! ```
120//!
121//! # Infinite and oscillatory integrals
122//!
123//! The current algorithms operate on finite contour pieces.
124//!
125//! Infinite-domain integrals should be handled by truncating the domain,
126//! providing a custom parameterised contour piece, or using problem-specific
127//! transformations. Highly oscillatory integrals may require manual splitting
128//! at known periods or specialized quadrature strategies.
129//!
130//! # Examples
131//!
132//! The `examples/` directory includes demonstrations of:
133//!
134//! - Gaussian quadrature over a real interval,
135//! - vector-valued integration,
136//! - Fresnel-type oscillatory integrals,
137//! - Cauchy's integral formula,
138//! - residue-theorem calculations,
139//! - indented pole contours,
140//! - Sommerfeld-style branch-point integrals,
141//! - Bromwich inversion,
142//! - half-disk Fourier contours.
143//!
144//! # Crate structure
145//!
146//! Most users only need:
147//!
148//! - [`integrate_real`],
149//! - [`integrate_complex`],
150//! - [`IntegratorConfig`],
151//! - [`Integrable`],
152//! - [`Contour`] and related contour constructors.
153//!
154//! Lower-level types such as segment heaps and Gauss–Kronrod internals are
155//! implementation details.
156
157#![allow(dead_code)]
158#![allow(clippy::type_complexity)]
159#[warn(clippy::all)]
160#[warn(missing_docs)]
161mod config;
162mod contour;
163mod core;
164mod integrable;
165mod output;
166mod solve;
167mod state;
168mod storage;
169
170pub use config::IntegratorConfig;
171pub use contour::{CircularArc, Contour, ContourSegment, IndentSide, LineSegment};
172pub use core::IntegratorError;
173pub use integrable::{ComplexScalar, FallibleIntegrable, Integrable, IntegrableFloat};
174pub use output::{ErrorNorm, IntegrationOutput};
175
176use integrable::Infallible;
177pub(crate) use state::IntegrationSummary;
178
179pub(crate) use contour::ContourPiece;
180use solve::Integrator;
181pub(crate) use state::IntegrationState;
182pub(crate) use storage::SegmentHeap;
183
184use nalgebra::ComplexField;
185use std::ops::Range;
186use trellis_runner::{
187    AbsoluteTolerancePolicy, EngineFailure, GenerateBuilderFallible, RelativeTolerancePolicy,
188    RunSummary, Termination,
189};
190
191pub struct IntegrationResult<I, O, F> {
192    pub integral: O,
193    pub error: F,
194    pub evaluations: usize,
195    pub refinements: usize,
196    pub termination: Termination,
197    pub summary: RunSummary<F>,
198    pub samples: Option<crate::core::QuadratureSamples<I, O>>,
199}
200
201impl<I, O, F> IntegrationResult<I, O, F> {
202    fn from_parts(
203        result: IntegrationSummary<I, O, F>,
204        summary: RunSummary<F>,
205        termination: Termination,
206    ) -> Self {
207        Self {
208            integral: result.integral,
209            error: result.error,
210            evaluations: result.evaluations,
211            refinements: result.refinements,
212            termination,
213            summary,
214            samples: result.samples,
215        }
216    }
217}
218
219pub fn integrate_complex<F, P>(
220    problem: P,
221    contour: Contour<F>,
222    config: IntegratorConfig<F>,
223) -> Result<
224    IntegrationResult<P::Input, P::Output, F>,
225    IntegratorError<P::Input, std::convert::Infallible>,
226>
227where
228    F: IntegrableFloat + ComplexScalar,
229    P: Integrable<Float = F, Input = <F as ComplexScalar>::Complex>,
230    <P as Integrable>::Output: IntegrationOutput<P::Input, Float = F>,
231{
232    let contour = config.deform_contour(contour);
233
234    let integrator = Integrator::complex_contour(contour, &config);
235
236    integrator
237        .build_for(Infallible(problem))
238        .with_initial_state(IntegrationState::new())
239        .and_policy(AbsoluteTolerancePolicy::new(
240            config.absolute_tolerance,
241            config.tolerance_window,
242        ))
243        .and_policy(RelativeTolerancePolicy::new(
244            config.relative_tolerance,
245            config.tolerance_window,
246        ))
247        .finalise()
248        .run()
249        .map(|output| {
250            IntegrationResult::from_parts(output.result, output.summary, output.termination)
251        })
252        .map_err(|EngineFailure::Procedure { error, state: _ }| error)
253}
254
255pub fn integrate_interval<F, P>(
256    problem: P,
257    interval: Range<F>,
258    config: IntegratorConfig<F>,
259) -> Result<IntegrationResult<F, P::Output, F>, IntegratorError<F, std::convert::Infallible>>
260where
261    F: IntegrableFloat + ComplexField<RealField = F>,
262    P: Integrable<Float = F, Input = F>,
263    <P as Integrable>::Output: IntegrationOutput<P::Input, Float = F>,
264{
265    integrate_real(problem, vec![interval.start, interval.end], config)
266}
267
268pub fn integrate_real<F, P>(
269    problem: P,
270    points: Vec<F>,
271    config: IntegratorConfig<F>,
272) -> Result<IntegrationResult<F, P::Output, F>, IntegratorError<F, std::convert::Infallible>>
273where
274    F: IntegrableFloat + ComplexField<RealField = F>,
275    P: Integrable<Float = F, Input = F>,
276    <P as Integrable>::Output: IntegrationOutput<P::Input, Float = F>,
277{
278    let integrator = Integrator::real_piecewise_linear(points, &config);
279
280    integrator
281        .build_for(Infallible(problem))
282        .with_initial_state(IntegrationState::new())
283        .and_policy(AbsoluteTolerancePolicy::new(
284            config.absolute_tolerance,
285            config.tolerance_window,
286        ))
287        .and_policy(RelativeTolerancePolicy::new(
288            config.relative_tolerance,
289            config.tolerance_window,
290        ))
291        .finalise()
292        .run()
293        .map(|output| {
294            IntegrationResult::from_parts(output.result, output.summary, output.termination)
295        })
296        .map_err(|EngineFailure::Procedure { error, state: _ }| error)
297}
298
299pub fn integrate_complex_fallible<F, P>(
300    problem: P,
301    contour: Contour<F>,
302    config: IntegratorConfig<F>,
303) -> Result<IntegrationResult<P::Input, P::Output, F>, IntegratorError<P::Input, P::Error>>
304where
305    F: IntegrableFloat + ComplexScalar,
306    P: FallibleIntegrable<Float = F, Input = <F as ComplexScalar>::Complex>,
307    <P as FallibleIntegrable>::Output: IntegrationOutput<P::Input, Float = F>,
308{
309    let contour = config.deform_contour(contour);
310
311    let integrator = Integrator::complex_contour(contour, &config);
312
313    integrator
314        .build_for(problem)
315        .with_initial_state(IntegrationState::new())
316        .and_policy(AbsoluteTolerancePolicy::new(
317            config.absolute_tolerance,
318            config.tolerance_window,
319        ))
320        .and_policy(RelativeTolerancePolicy::new(
321            config.relative_tolerance,
322            config.tolerance_window,
323        ))
324        .finalise()
325        .run()
326        .map(|output| {
327            IntegrationResult::from_parts(output.result, output.summary, output.termination)
328        })
329        .map_err(|EngineFailure::Procedure { error, state: _ }| error)
330}
331
332pub fn integrate_interval_fallible<F, P>(
333    problem: P,
334    interval: Range<F>,
335    config: IntegratorConfig<F>,
336) -> Result<IntegrationResult<F, P::Output, F>, IntegratorError<F, P::Error>>
337where
338    F: IntegrableFloat + ComplexField<RealField = F>,
339    P: FallibleIntegrable<Float = F, Input = F>,
340    <P as FallibleIntegrable>::Output: IntegrationOutput<P::Input, Float = F>,
341{
342    integrate_real_fallible(problem, vec![interval.start, interval.end], config)
343}
344
345pub fn integrate_real_fallible<F, P>(
346    problem: P,
347    points: Vec<F>,
348    config: IntegratorConfig<F>,
349) -> Result<IntegrationResult<F, P::Output, F>, IntegratorError<F, P::Error>>
350where
351    F: IntegrableFloat + ComplexField<RealField = F>,
352    P: FallibleIntegrable<Float = F, Input = F>,
353    <P as FallibleIntegrable>::Output: IntegrationOutput<P::Input, Float = F>,
354{
355    let integrator = Integrator::real_piecewise_linear(points, &config);
356
357    integrator
358        .build_for(problem)
359        .with_initial_state(IntegrationState::new())
360        .and_policy(AbsoluteTolerancePolicy::new(
361            config.absolute_tolerance,
362            config.tolerance_window,
363        ))
364        .and_policy(RelativeTolerancePolicy::new(
365            config.relative_tolerance,
366            config.tolerance_window,
367        ))
368        .finalise()
369        .run()
370        .map(|output| {
371            IntegrationResult::from_parts(output.result, output.summary, output.termination)
372        })
373        .map_err(|EngineFailure::Procedure { error, state: _ }| error)
374}