This namespace holds an implementation of "adaptive quadrature" usable with any quadrature method in the library.
The implementation was inspired by the numerics/quadrature/rational.scm
file in the scmutils package. In that library, adaptive quadrature was special-cased to the Bulirsch-Stoer algorithm, ported in bulirsch_stoer.cljc
.
Most of the integrators in quadrature
work by successively refining an integration interval Loading... down into evenly-spaced integration slices. Some functions are very well behaved in some regions, and then oscillate wildly in others.
Adaptive quadrature methods partition Loading... into intervals of different sizes, allowing the integrator to drill in more closely to areas that need attention.
The adaptive
implementation below works like this:
use a wrapped integrate
function on the full Loading..., capping the iterations at some configurable limit (*adaptive-maxterms*
below)
If integrate
fails to converge, split Loading... into two intervals and attempt to converge both sides using integrate
Continue this process until convergence, or until the interval becomes small enough to fail the test in common/narrow-slice?
, at which point integrate
returns an estimate for a single slice.
The *adaptive-maxterms*
variable is dynamic, which means you can adjust the behavior of adaptive
by supplying the :maxterms
option directly, or wrapping your call in (binding [*adaptive-maxterms* 8] ,,,)
.
Symmetric intervals like Loading... often show up with integrands with singularities right at the center of the midpoint. For this reason, adaptive
is able to customize its splitting behavior using the *neighborhood-width*
dynamic variable.
By default, when partitioning an interval, adaptive
will choose an interval within 5% of the midpoint. Override this behavior with the :adaptive-neighborhood-width
key in the options dict, or by binding this dynamic variable.
The implementation below takes /two/ integrate functions, not the one described above. This allows us to handle open and closed intervals, instead of introducing open endpoints at every subdivision. All internal intervals that don't touch an open endpoint are considered closed.
adaptive
runs until it completes, with no facility available to bail out of computation. An iteration limit would be a great addition... but it won't be efficient without some way of prioritizing high-error subintervals for refinement, as discussed next.
Another nice upgrade would be a version of adaptive
that is able to return an actual sequence of successive refinements. to the estimate. The current implementation uses an internal stack to track the subdivided intervals it needs to evaluate. If the integrator was able to provide an error estimate, we could instead use a priority queue, prioritized by error.
adaptive-sequence
could then return a sequence where every element processes a single refinement. Even without this upgrade, the priority queue idea would allow the estimate to converge quickly and be more accurate if we bailed out at some max number of iterations.
This article holds a related implementation: http://www.learningclojure.com/2011/05/numerical-integration-better_29.html