Description
Overview
Let's get numerical integration into stdlib. As a baseline, here's what I'd want in a general-purpose integration module.
- Interfaces to QUADPACK routines, both simple wrappers and a more modern interface a la SciPy's
scipy.integrate.quad
. - Routines to integrate arrays as if they are tabulated function values. At a minimum, we'd want trapezoid rule and Simpson's rule.
- Some niche or less well-known rules that are still useful. Examples might be endpoint-corrected discrete Fourier transforms (see Numerical Recipes Sec. 13.9) and double-exponential rules.
The idea would be to provide easy-to-use and reasonably general numerical integration routines that cover 95% of what people need. Some things which are out of scope in my opinion:
- ODE integration
- Monte Carlo integration
- Basis representations of abstract integral operators
I envision having two modules: one being a light f90 wrapper for QUADPACK which expert users can use, the other being the main module with a nicer interface to QUADPACK's functionality, as well as array-integrating routines.
Integrating functions
I've already written wrappers and tests for "non-expert" QUADPACK routines (link). All these do is create explicit interfaces and give names to magic numbers, e.g., error codes and integrand weight selectors. It will need a little refactoring to port to stdlib, but I am pretty confident that my module quadpack_interface
uses all the right types, kinds, and intents to agree with the F77 routines. No need to duplicate that effort here.
From a modern programmer's perspective, QUADPACK has some unattractive qualities:
- Explicitly passed workspace arrays with sizes requirements that are documented but not enforced in code. These mattered in the days of tight memory constraints, but nowadays, we can relieve the caller of this burden.
- Out-parameters that are not usually useful should be made optional.
- Technical-detail in-parameters should be made optional with sensible default values.
- The integrand function must be a scalar function of one variable. This can make it clumsy to integrate functions with additional parameters, and doing multiple integrals by nested QUADPACK calls gets cumbersome.
- Routines for weighted integrands can be greatly simplified and clarified. For instance,
qaws
implements weight functions that remove algebraic/logarithmic singularities at the integration endpoints. The type of singularity is controlled by an integer argument, whose value affects the meaning of other arguments.
Solving these issues should be the motivating goal of a module that delivers high-level integration functions that call down to the appropriate QUADPACK subroutines, internally handling the magic numbers, workspace array allocation, and translating return codes to proper runtime errors if necessary. I have some design sketches that I'll talk about in a later post.
Integrating arrays
Integrating arrays is a simpler task. We just need to decide how featureful we want to be with this. For reference, NumPy/SciPy provide
numpy.trapz
: trapezoidal rule with either equidistant or arbitrary abscissas.scipy.simps
: Simpson's rule with either equidistant or arbitrary abscissas.scipy.romb
: Romberg integration, which is restricted to equidistant abscissas whose length is2**k + 1
, e.g., 1, 3, 5, 9, 17...scipy.cumtrapz
: cumulative trapezoidal rule with optional initial value
We should have all these, except maybe Romberg integration. I don't think I have ever used it "for real", but maybe other people can make a case for keeping it. To this basic set of routines, I'd also like to add companion functions to trapz
and simps
that return the weights for a particular array of abscissas. This is useful for integrating many different arrays that represent functions tabulated on a common grid. Once you have the weights from, e.g., w = trapz_weights(x)
, then computing the integral is as simple as integral = dot_product(y, w)
.
Details
- Are there any licensing show-stoppers with QUADPACK? You always wonder with these Netlib libraries.
- We need to decide (elsewhere) what conventions to adopt for wrapping F77 codes. Relevant aspects here are
- translating
real
anddouble precision
to appropriate real kinds - explicit interfaces for
external
procedure - managing workspace arrays (Module-level or subprogram-level? With or without
save
?)
- translating