Skip to content

Declare bound_inclusive on every primitive quadrature - #119

Merged
wavefunction91 merged 3 commits into
wavefunction91:masterfrom
susilehtola:fix/primitive-bound-inclusive
Oct 12, 2026
Merged

wavefunction91 merged 3 commits into
wavefunction91:masterfrom
susilehtola:fix/primitive-bound-inclusive

Conversation

@susilehtola

Copy link
Copy Markdown
Collaborator

Fixes #116.

RadialTransformQuadrature reads base_quad_traits::bound_inclusive to decide
whether to generate two extra nodes and drop the endpoints, but only
gausschebyshev2 and uniform declared it. The other five failed to compile
when used as a radial base:

error: 'bound_inclusive' is not a member of 'base_quad_traits'

so a radial transform could be composed only with those two, although the
README describes radial quadratures as convolutions of a primitive with a
transformation — which reads as a composition matrix.

The value is whether the rule places nodes on the interval endpoints: false
for the open rules (gausschebyshev1, gausschebyshev2modified,
gausschebyshev3, gausslegendre) and true for gausslobatto, which
includes both by construction. gausslobatto is the one that matters beyond
bookkeeping — the only primitive here whose value is true, and the natural
base for a Gauss-Lobatto x uniform-phi product sphere rule.

Verified

All 28 primitive-transform pairs compile. The domain-matched ones integrate
r^2 exp(-r) over (0, inf) correctly at 120 points, e.g.

GaussChebyshev2Modified x Becke              0.00e+00
GaussChebyshev2Modified x TreutlerAhlrichs   6.66e-16
GaussLegendre           x Becke              0.00e+00
GaussLobatto            x Becke              0.00e+00
GaussChebyshev3         x MurrayHandyLaming  8.88e-16

The mismatched pairs compile but are meaningless — Becke and
Treutler-Ahlrichs map from (-1,1) while Mura-Knowles and
Murray-Handy-Laming map from (0,1). The traits member says nothing about
domain, only about endpoints, so this PR does not make every pair valid, only
every pair expressible. Guarding the domain would be a separate change.

Behaviour-neutral for everything shipped: the four radial quadratures are
bit-identical to master across 392 node and weight values, since the two
primitives they use already declared the member. Suite 9/9.

Not done here

The requirement is duck-typed: a new primitive fails only when someone tries to
compose it. A defaulted member in a traits base, or a static_assert with a
detection idiom in RadialTransformQuadrature, would stop this recurring. I
left that out as a separate design question rather than bundling it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF

RadialTransformQuadrature reads base_quad_traits::bound_inclusive to decide
whether to generate two extra nodes and drop the endpoints, but only
gausschebyshev2 and uniform declared it. The other five failed to compile
when used as a radial base:

    error: 'bound_inclusive' is not a member of 'base_quad_traits'

So a radial transform could be composed only with those two, although the
README describes radial quadratures as convolutions of a primitive with a
transformation, which reads as a composition matrix.

The value is whether the rule places nodes on the interval endpoints:
false for the open rules -- gausschebyshev1, gausschebyshev2modified,
gausschebyshev3 and gausslegendre -- and true for gausslobatto, which
includes both endpoints by construction. gausslobatto is the one that
matters beyond bookkeeping: it is the only primitive here whose value is
true, and the natural base for a Gauss-Lobatto x uniform-phi product
sphere rule.

All 28 primitive-transform pairs now compile. The domain-matched ones
integrate r^2 exp(-r) over (0, inf) correctly, for instance at 120 points:

    GaussChebyshev2Modified x Becke              0.00e+00
    GaussChebyshev2Modified x TreutlerAhlrichs   6.66e-16
    GaussLegendre           x Becke              0.00e+00
    GaussLobatto            x Becke              0.00e+00
    GaussChebyshev3         x MurrayHandyLaming  8.88e-16

The mismatched pairs compile but are meaningless, since Becke and
Treutler-Ahlrichs map from (-1,1) while Mura-Knowles and
Murray-Handy-Laming map from (0,1); the traits member says nothing about
domain, only about endpoints.

Behaviour-neutral for everything shipped: the four radial quadratures are
bit-identical to master over 392 node and weight values, since the two
primitives they use already declared the member.

Fixes wavefunction91#116.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF

@wavefunction91 wavefunction91 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See inline comments.


/// Whether the rule places nodes on the interval endpoints.
/// Lobatto includes both endpoints by construction.
inline static constexpr bool bound_inclusive = true;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check in the description (all 28 pairs compiling, r²e⁻ʳ on the domain-matched ones) is exactly the regression guard for #116, but it isn't in the suite. Nothing currently instantiates RadialTransformQuadrature over any of these five primitives. Could we add a case to test/1d_quadratures.cxx next to the Becke/MHL tests, e.g. test_quadrature<RadialGaussian> on RadialTransformQuadrature<GaussLobatto<double,double>, BeckeRadialTraits> and on GaussLegendre × Becke? Lobatto is the only true among them, so it would also exercise the endpoint-dropping path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 00061bc, as a new "Radial transforms over the primitive rules" case: r^2 exp(-r) on Gauss-Lobatto x Becke and Gauss-Legendre x Becke, plus the full 28-pair instantiation sweep from the description. Against master's primitives the sweep fails with "bound_inclusive is not a member of base_quad_traits", so it guards exactly this. Lobatto covers the endpoint-dropping path as you note.

One wrinkle worth recording: MurrayHandyLamingRadialTraits is templated on the exponent, so the sweep pins it at <2>.

using point_container = std::vector<point_type>;
using weight_container = std::vector<weight_type>;

/// Whether the rule places nodes on the interval endpoints.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two existing declarations (gausschebyshev2.hpp:61, uniform.hpp:46) are bare inline static constexpr bool bound_inclusive = ...; lines, while this adds the same two-line /// comment to each of the five new ones. Could we match the existing ones and drop these? The reason for each value is already visible in generate() just below.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped from all five, so they now match the existing bare declarations.

The declarations now read like the two that were already in the tree
(gausschebyshev2.hpp, uniform.hpp): bare one-liners, with the reason for
each value left to generate() just below.

The check from the PR description was not in the suite. Nothing instantiated
RadialTransformQuadrature over any of these primitives, since the named
radial grids are all fixed aliases over GaussChebyshev2, so the capability
this unblocks was untested. Add the full 28-pair sweep, which fails to
compile without the declarations ("bound_inclusive is not a member of
base_quad_traits"), plus r^2 exp(-r) on Gauss-Lobatto x Becke and
Gauss-Legendre x Becke. Lobatto is the only bound_inclusive rule among
them, so it also exercises the endpoint-dropping path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF

@wavefunction91 wavefunction91 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See inline comment.

Comment thread test/1d_quadratures.cxx Outdated

TEST_CASE( "Radial transforms over the primitive rules", "[1d-quad]" ) {

// The named radial grids are all fixed aliases over GaussChebyshev2, so

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mura-Knowles and MHL are aliases over UniformTrapezoid, not GaussChebyshev2, and UniformTrapezoid is bound_inclusive too (uniform.hpp:46), so the existing MK/MHL tests already exercise endpoint dropping; Lobatto is the only bound-inclusive Gauss rule.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right on both counts — MuraKnowles and MurrayHandyLaming are RadialTransformQuadrature<UniformTrapezoid<...>, ...>, only Becke and Treutler-Ahlrichs go through GaussChebyshev2, and uniform.hpp:46 does declare bound_inclusive = true. So endpoint dropping was already covered and Lobatto is the only bound-inclusive Gauss rule.

Corrected in ef50142, comments only. The commit message on 00061bc carries the same wrong claim, which I have left alone rather than rewrite the branch.

Becke and Treutler-Ahlrichs are aliases over GaussChebyshev2, but
Mura-Knowles and MHL are over UniformTrapezoid, which is bound_inclusive
itself, so the existing MK and MHL cases already exercise endpoint
dropping. Lobatto is the only bound_inclusive Gauss rule, not the only
bound_inclusive primitive. Comments only; the cases are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF
@wavefunction91
wavefunction91 merged commit f4af7ca into wavefunction91:master Oct 12, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Five of seven primitives do not declare bound_inclusive, so they cannot be used with RadialTransformQuadrature

2 participants