Skip to content

Fix container type and empty-vector defects in the composite quadratures - #105

Merged
wavefunction91 merged 2 commits into
wavefunction91:masterfrom
susilehtola:fix/subquadrature-and-partition
Oct 8, 2026
Merged

wavefunction91 merged 2 commits into
wavefunction91:masterfrom
susilehtola:fix/subquadrature-and-partition

Conversation

@susilehtola

Copy link
Copy Markdown
Collaborator

Three unrelated defects in the same two headers.

SubQuadrature's generator declared its weights in the point container
type and returned it as the weight_container half of the tuple:

point_container points ( ... );
point_container weights( ... );   // wrong type

This compiles today only because every radial quadrature happens to use
std::vector for both. It breaks as soon as the two differ, which
is exactly what mixed precision needs: SubQuadrature<Becke<float,double>>
does not compile before this change. Two of the surrounding assertions
also tested >= 0 on size_t, which is vacuous; drop them and keep the
bounds that say something.

RadialGridPartition::finalize() read partition_idx_.back() without
checking that anything had been added, which is undefined behaviour on an
empty vector. make_pruned_grid() guards against an empty region list one
layer up, but finalize() is public and the class is default-constructible,
so the guard is not structural. Throw instead.

Six adaptor overrides in the two spherical quadrature headers were
written as override {; -- a stray empty statement after the opening
brace. Harmless, but remove them.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF

Three unrelated defects in the same two headers.

SubQuadrature's generator declared its weights in the *point* container
type and returned it as the weight_container half of the tuple:

    point_container points ( ... );
    point_container weights( ... );   // wrong type

This compiles today only because every radial quadrature happens to use
std::vector<double> for both. It breaks as soon as the two differ, which
is exactly what mixed precision needs: SubQuadrature<Becke<float,double>>
does not compile before this change. Two of the surrounding assertions
also tested `>= 0` on size_t, which is vacuous; drop them and keep the
bounds that say something.

RadialGridPartition::finalize() read partition_idx_.back() without
checking that anything had been added, which is undefined behaviour on an
empty vector. make_pruned_grid() guards against an empty region list one
layer up, but finalize() is public and the class is default-constructible,
so the guard is not structural. Throw instead.

Six adaptor overrides in the two spherical quadrature headers were
written as `override {;` -- a stray empty statement after the opening
brace. Harmless, but remove them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF
@susilehtola
susilehtola force-pushed the fix/subquadrature-and-partition branch from fbf3a2d to dbbf796 Compare September 4, 2026 07:54

@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.


template <typename RadialQuad>
void finalize( const RadialQuad& rq ) {
if( partition_idx_.empty() )

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 same default-constructed partition still hits UB through the iterators: end(), cend() and end() const (lines 141/148/155) compute partition_idx_.end()-1, which on an empty vector steps before begin(). With -D_GLIBCXX_DEBUG on this head, RadialGridPartition<LebedevLaikov<double>>{}.end() aborts with "attempt to retreat a past-the-end iterator". Since this PR is about the empty-vector cases, could those be made safe here too? A REQUIRE_THROWS next to the default-constructed rgp in test/composite_quadratures.cxx:202 would also pin the new finalize() behaviour.

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.

You are right, and the iterators were the live path. Fixed in 7ab221b: end(), cend() and end() const present an empty range when partition_idx_ is empty instead of computing end()-1. I reproduced your abort first — -D_GLIBCXX_DEBUG on master gives "attempt to retreat a past-the-end iterator" from RadialGridPartition<LebedevLaikov<double>>{}.end(), and the patched version reports an empty range.

The new "Unfinalized Partition" section pins REQUIRE_THROWS( rgp.finalize(r) ) together with begin() == end() for all three overloads.


point_container points ( quad.points().begin() + begin_idx,
quad.points().begin() + end_idx );
weight_container weights( quad.weights().begin() + begin_idx,

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.

Nothing in the suite instantiates SubQuadrature, so the mixed-precision case this fixes isn't covered, and it would regress silently. Could we add a small case, e.g. SubQuadrature<Becke<float,double>> over a slice of a Becke<float,double>, checking that the points and (double) weights match the parent's slice?

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 as a "Sub Quadratures" case over Becke<float,double>, checking the slice against the parent exactly, plus static_asserts on both container types.

One correction to the premise, in the good direction: it would not have regressed silently. With master's sub_quadrature.hpp the instantiation does not compile at all — make_tuple yields tuple<vector<float>,vector<float>>, which will not convert to tuple<vector<float>,vector<double>>. So the case was simply absent rather than passing for the wrong reason.

quad.points().begin() + end_idx );
point_container weights( quad.weights().begin() + begin_idx,
quad.weights().begin() + end_idx );
// begin_idx and end_idx are size_t, so the lower bounds are vacuous

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.

This comment describes the asserts that were removed rather than the code that remains. Could we drop it?

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.

finalize() now refuses an empty partition, but the same default-constructed
object still reached undefined behaviour through end(), cend() and
end() const: each computes partition_idx_.end()-1 to skip the npts
sentinel, which steps before begin() when there are no entries at all.
Under -D_GLIBCXX_DEBUG, RadialGridPartition<LebedevLaikov<double>>{}.end()
aborts with "attempt to retreat a past-the-end iterator". Present an empty
range in that case instead.

Both fixes were untested. The new "Unfinalized Partition" section pins the
finalize() throw and the empty range; the new "Sub Quadratures" case covers
the container type, which nothing in the suite instantiated. The latter is
not a silent regression but a hard one: with master's sub_quadrature.hpp,
SubQuadrature<Becke<float,double>> fails to compile, since the weights are
built as a vector<float> that make_tuple cannot convert to vector<double>.

Also drop a comment that described the removed asserts rather than the
code left behind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FDTFYJMQ76iujDFNHzZyXF
@wavefunction91
wavefunction91 merged commit bea7af3 into wavefunction91:master Oct 8, 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.

2 participants