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, Integrable, IntegrableFloat};
174pub use output::{ErrorNorm, IntegrationOutput};
175
176pub(crate) use state::IntegrationSummary;
177
178pub(crate) use contour::ContourPiece;
179use solve::Integrator;
180pub(crate) use state::IntegrationState;
181pub(crate) use storage::SegmentHeap;
182
183use nalgebra::ComplexField;
184use std::ops::Range;
185use trellis_runner::{
186    AbsoluteTolerancePolicy, EngineFailure, GenerateBuilderFallible, RelativeTolerancePolicy,
187    RunSummary, Termination,
188};
189
190pub struct IntegrationResult<I, O, F> {
191    pub integral: O,
192    pub error: F,
193    pub evaluations: usize,
194    pub refinements: usize,
195    pub termination: Termination,
196    pub summary: RunSummary<F>,
197    pub samples: Option<crate::core::QuadratureSamples<I, O>>,
198}
199
200impl<I, O, F> IntegrationResult<I, O, F> {
201    fn from_parts(
202        result: IntegrationSummary<I, O, F>,
203        summary: RunSummary<F>,
204        termination: Termination,
205    ) -> Self {
206        Self {
207            integral: result.integral,
208            error: result.error,
209            evaluations: result.evaluations,
210            refinements: result.refinements,
211            termination,
212            summary,
213            samples: result.samples,
214        }
215    }
216}
217
218pub fn integrate_complex<F, P>(
219    problem: P,
220    contour: Contour<F>,
221    config: IntegratorConfig<F>,
222) -> Result<IntegrationResult<P::Input, P::Output, F>, IntegratorError<P::Input>>
223where
224    F: IntegrableFloat + ComplexScalar,
225    P: Integrable<Float = F, Input = <F as ComplexScalar>::Complex>,
226    <P as Integrable>::Output: IntegrationOutput<P::Input, Float = F>,
227{
228    let contour = config.deform_contour(contour);
229
230    let integrator = Integrator::complex_contour(contour, &config);
231
232    integrator
233        .build_for(problem)
234        .with_initial_state(IntegrationState::new())
235        .and_policy(AbsoluteTolerancePolicy::new(
236            config.absolute_tolerance,
237            config.tolerance_window,
238        ))
239        .and_policy(RelativeTolerancePolicy::new(
240            config.relative_tolerance,
241            config.tolerance_window,
242        ))
243        .finalise()
244        .run()
245        .map(|output| {
246            IntegrationResult::from_parts(output.result, output.summary, output.termination)
247        })
248        .map_err(|EngineFailure::Procedure { error, state: _ }| error)
249}
250
251pub fn integrate_interval<F, P>(
252    problem: P,
253    interval: Range<F>,
254    config: IntegratorConfig<F>,
255) -> Result<IntegrationResult<F, P::Output, F>, IntegratorError<F>>
256where
257    F: IntegrableFloat + ComplexField<RealField = F>,
258    P: Integrable<Float = F, Input = F>,
259    <P as Integrable>::Output: IntegrationOutput<P::Input, Float = F>,
260{
261    integrate_real(problem, vec![interval.start, interval.end], config)
262}
263
264pub fn integrate_real<F, P>(
265    problem: P,
266    points: Vec<F>,
267    config: IntegratorConfig<F>,
268) -> Result<IntegrationResult<F, P::Output, F>, IntegratorError<F>>
269where
270    F: IntegrableFloat + ComplexField<RealField = F>,
271    P: Integrable<Float = F, Input = F>,
272    <P as Integrable>::Output: IntegrationOutput<P::Input, Float = F>,
273{
274    let integrator = Integrator::real_piecewise_linear(points, &config);
275
276    integrator
277        .build_for(problem)
278        .with_initial_state(IntegrationState::new())
279        .and_policy(AbsoluteTolerancePolicy::new(
280            config.absolute_tolerance,
281            config.tolerance_window,
282        ))
283        .and_policy(RelativeTolerancePolicy::new(
284            config.relative_tolerance,
285            config.tolerance_window,
286        ))
287        .finalise()
288        .run()
289        .map(|output| {
290            IntegrationResult::from_parts(output.result, output.summary, output.termination)
291        })
292        .map_err(|EngineFailure::Procedure { error, state: _ }| error)
293}