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}