add integrate_1d_gauss_kronrod#3326
Conversation
|
Would it make sense to add a suffixed alias of the existing integrate_1d as part of this? I think we have done similar in the past when adding e.g. a second kind of algebra solver |
|
If we would add |
|
That would also be good, then. If I recall correctly these functions are also already ready to be variadic at the math level but just aren’t in the language, should we make these new versions variadic from the start? |
|
I' m confident I can get Claude to create |
|
In the existing code (and this PR, looking over it briefly) the _impl function already is variadic. So we would just rename that to drop the _impl, remove the adaptor struct wrapping F, and we should be good to go. I can help with this (and the stanc stuff, since it gets slightly more complicated to add a variadic), but I think it’s worth doing if we’re introducing new names anyway |
|
I can add |
|
Ah, didn't see your latest comment. I can add |
|
Sounds good. As long as claude follows the lead on the existing code, the extra steps to make it variadic should be very easy. The main reason it hasn’t been so far is that we need to come up with a new name for that version, which you’re already doing! |
Closes #3000.
Summary
Adds
integrate_1d_gauss_kronrod, a 1-D quadrature function that wrapsBoost's adaptive Gauss-Kronrod (
boost/math/quadrature/gauss_kronrod.hpp,already in the vendored Boost 1.87.0) in the (almost) same user-facing API shape
as
integrate_1d. The two functions cover disjoint weaknesses and liveside by side:
integrate_1d(tanh_sinh / exp_sinh / sinh_sinh) excels at integrandswith algebraic or logarithmic endpoint singularities; its
double-exponential nodes concentrate near the endpoints.
integrate_1d_gauss_kronrod(K21 with adaptive bisection) excels atsmooth integrands and at integrands with peaks off-centre in the
integration interval — exactly the case where double-exponential
quadrature undersamples and
integrate_1dcan return silently-zeroor wildly-imprecise values.
The API is the same as with
integrate_1dexcept addingabs_tolandmax_deptharguments.My use case is integrals of functions which are product of normal
density and a smooth function. Due to the light tails of the normal
density this functions goes to zero in tails and Gauss-Kronrod excels
compare to double exponential. I really needed this for some
experiments I'm running, and
abs_tolwas also required to get rid ofnumerical problems.
Public API
C++ (prim/rev/fwd autodiff layers):
Stan language — four overloads, parameter additions trailing:
Convergence test is QUADPACK-style mixed:
With
absolute_tolerance = 0(default) this reduces to the strictpure-relative behaviour of
integrate_1d.User integrand functor signature is unchanged from
integrate_1d(
(x, xc, theta, x_r, x_i, msgs) -> real);xcis unused underGauss-Kronrod (always passed as
NaN).Design notes
scipy.integrate.quad,MATLAB's
quad, QUADPACK'sdqagwith default key. Polynomialexactness up to degree 31 covers the Gaussian-times-likelihood
shapes that dominate Stan use. Higher N tightens node spacing for
hard peaks but at 1.5x-3x cost on smooth integrands; we chose to
address robustness on hard cases through
absolute_toleranceandinformed bound choice instead.
absolute_toleranceargument. Boost's gauss_kronrod accumulateserror += error_localacross adaptive subdivisions, with eacherror_localcarrying a floor2 * eps * |K_local|. Theaccumulated floor scales as
~2^max_depth * eps * L1, about7e-12 * L1at the defaultmax_depth = 15. For integrals whoseL1falls below~1e-8(e.g. nested calls where the outerevaluates the integrand deep in the tail of a Gaussian factor),
the strict pure-relative test measures noise against noise and
throws spuriously. QUADPACK-style mixed convergence is the
established fix.
max_depthexposed. Default 15 matches Boost; user can raise itfor hard adaptive cases without re-implementing the wrapper.
test suite (a
endpoint_singularity_throwstest asserts that GKthrows on
1/sqrt(x),1/sqrt(1-x), and beta-with-small-shapesintegrands). Users with these integrands should keep using
integrate_1d.Implementation layout
The user-facing functor signature is adapted through the existing
integrate_1d_adapter(no new adapter needed). stanc3 changes will be in acompanion PR.
Testing
Tests are ported from
integrate_1d's counterparts with adaptationsto drop the
xc-based functors; new tests cover theabsolute_toleranceandmax_deptharguments and document theendpoint-singularity weakness.
overloads on integrals with known closed forms; rev autodiff is
verified by integrating a Normal PDF with a
parameters-blockmuacross 200 MCMC draws (returns exactly 1 each time).
over a 2-D random-effects vector, 144 observations, 4 chains x 100
draws) produces ELPD estimates that agree with independent bridge
sampling to 0.05 nats per fold at the 99th percentile. For the
same case, the existing
integrate_1dwas silently returning zeros.Possible future work
The Kronrod order N is currently fixed at 21 in the wrapper. Boost
provides compile-time tables for
N in {15, 21, 31, 41, 51, 61}, andexposing N as an optional
data intparameter via a smallruntime-to-compile-time dispatch (a
switch(N)over the six validvalues) is a clean extension that doesn't break the current overload
set. Left out from this PR
Companion PRs
integrate_1d_gauss_kronrodsignatures inStan_math_signatures.mland an|-alternative pattern extensionin
Lower_expr.ml'smsgs-injection clause.(deferred until this PR is approved).
integrate_1d(deferred until this PR is approved).
Checklist
Stan Development Team. The code is duplication of existing integrate_1d code and there is no additional creativity.
the basic tests are passing
./runTests.py test/unit)make test-headers)make test-math-dependencies)make doxygen)make cpplint)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested
AI Use Disclosure
The duplication of the existing
integrate_1dcode and tests was made using Claude AI Agent. The actual quadrature algorithm is in Boost library, and the code is just a wrapper to call Boost.