Repository navigation
Quadrature cap warning: report the real change, name the integral, and test both - #349
Merged
Merged
Conversation
converge_block's cap warning computed its relative change as (cur - prev) AFTER prev had been overwritten with cur three lines above, so it printed "relative change still 0.000e+00" unconditionally -- however badly a block was actually converging. Report prevdiff, the last difference the convergence test itself saw, and say so explicitly in the one case where the seed order already exceeded the cap and no difference was ever computed. With that fixed the warning immediately reported 1.191e-01 on diatomic-H2-hf-r, a CI case. Real, but harmless: radial_integral(-1,0) is the integral of B_i B_j / sinh(mu), which diverges logarithmically at mu = 0. The convergence history shows the block MAGNITUDE growing with the quadrature order (5.57 -> 6.96 -> 7.90) while every other block reaches 1e-15 at n=160, and the growth is exactly logarithmic: the 320 -> 512 step is a factor 1.6, and 1.39 * log(512/320)/log(2) = 0.94, the measured difference to two digits. The divergence is confined to the row and column of the one basis function that is non-zero at mu = 0 -- and that is exactly the function Nbf() drops from every m != 0 shell, which is the only place the integral is used. It is discarded before the matrix is ever seen, which is WHY it is dropped. So converge_block gains an nskip parameter excluding those entries from the convergence test (the block is still returned whole), and radial_integral passes it for m < 0. H2 is unchanged at -1.1336295702, the HF limit to all printed digits, and the warning no longer fires. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit makes the quadrature cap warning report the relative change
the convergence test actually saw, instead of a value that was identically
zero. This one makes that checkable, and fixes the other half of why the
warning was hard to act on.
WHAT THE WARNING COULD NOT SAY
Every weight passed to radial_integral reported as a bare "radial_integral",
so a cap could not be traced to its integrand. That produced a misattribution
worth recording, because the obvious reading is wrong.
A cap seen while building quadrupole_zz() -- whose I14 is radial_integral(1,4),
sinh(mu) cosh^4(mu) -- invites blaming (1,4). But (1,4) converges to machine
precision on the FIRST comparison across the whole focal-separation range, even
where the integrand spans eighteen orders of magnitude:
Rhalf mu_max (cosh mu_max)^5 (1,4) block scale rel at n=60
0.7 4.05 1.9e+07 1.8e+06 7.7e-16
0.005 8.99 1.0e+18 9.3e+16 3.4e-16
It is entire, and Gauss-Lobatto resolves it. The warning that actually fires is
kinetic()'s radial_integral(-1,0): 1/sinh(mu), logarithmically divergent at
mu = 0 in exactly the row and column remove_boundaries discards -- which the
previous commit already stops judging the block on. Its reported magnitude
(7.9 on diatomic-H2-hf-r, 8.1 in the diatomic CAS test) is the same class as a
cap of magnitude ~7 reported against a quadrupole build. Any Hamiltonian calls
kinetic(), so the label, not the integrand, was the problem. The warning now
names the (m, n).
MAKING IT TESTABLE
converge_block sat in an anonymous namespace in basis.cpp, and the numbers its
warning reports were computed INSIDE the one-shot print guard -- so after the
first cap in a process they were unobservable, and from outside basis.cpp
there was no way to reach them at all. A test that merely checks the warning
FIRES passes on the original bug.
So it moves to converge_block.h (it is a template over the probe; a header is
its natural home) and gains an optional CapReport, filled on every call
whether or not anything prints:
- rel is the change the test last saw, over the judged sub-block;
- rel == -1 means the cap was reached on the very first probe, so no
comparison was made -- previously also reported as 0, indistinguishable
from "the change was small";
- twoe_nmax / twoe_cap_warned become inline variables, one per program as
before.
converge_block_test drives it with probes known in closed form. The
non-converging one (every entry c*n) must report rel = (512-320)/512 = 0.375
exactly. It was verified to DISCRIMINATE: with the old (cur - prev)
computation reinstated it prints "relative change still 0.000e+00" and three
assertions fail.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The adaptive quadrature's cap warning reported a relative change that was identically zero, by construction. This fixes that, makes what it reports testable, and makes the warning say which integral it is about — which turns out to matter, because the obvious reading of the warning pointed at the wrong integrand.
1. The reported change was always zero
In
converge_block, the cap branch computed its relative change as(cur - prev)afterprev = cura few lines above — the difference of a matrix with itself. Every warning read:which says "converged perfectly" directly under a sentence saying the opposite. It now reports
prevdiff / scale, the last difference the convergence test itself saw. Where the cap is reached on the very first probe no comparison was ever made, and that now says so rather than also printing 0 — the old code conflated "no comparison" with "the change was small".2. The warning was firing spuriously
With the number honest, the warning turned out to fire on
diatomic-H2-hf-r, a CI case, reporting 1.2e-01. The source iskinetic()'sradial_integral(-1,0): the integrand1/sinh(μ)diverges logarithmically at μ = 0, so the block magnitude grows with quadrature order. The divergence is confined to the row and column of the one basis function non-zero at μ = 0 — exactly the functionremove_boundariesdrops from every m ≠ 0 shell, the only consumer. The block is now judged without those entries (nskip), and returned whole.H₂ is unchanged at −1.1336295702, the HF limit to all printed digits, and the warning no longer fires.
3. The label pointed at the wrong integrand
Every weight reported as a bare
radial_integral, so a cap couldn't be traced to its integrand. That invites a specific misreading.quadrupole_zz()needsradial_integral(1,4)=sinh(μ)cosh⁴(μ), and a cap seen while building it naturally gets blamed on(1,4). But(1,4)converges to machine precision on the first comparison across the entire focal-separation range, even where the integrand spans eighteen orders of magnitude:It's entire, and Gauss-Lobatto resolves it. The warning that actually fires is
kinetic()'s(-1,0), whose magnitude (7.9 on H₂, 8.1 in the diatomic CAS test) is the same class as a cap of magnitude ~7 reported against a quadrupole build — and any Hamiltonian callskinetic(). The warning now names the(m, n).4. Making it testable
converge_blocksat in an anonymous namespace inbasis.cpp, and the numbers its warning reports were computed inside the one-shot print guard — unobservable after the first cap in a process, and unreachable from outsidebasis.cppentirely. A test that merely checks the warning fires passes on the original bug.It moves to
converge_block.h(a template over the probe, so a header is its natural home) and gains an optionalCapReport, filled on every call whether or not anything prints.converge_block_testdrives it with probes known in closed form:c·nrel == (512−320)/512 = 0.375exactly, and > 0rel == −1: no comparison was madenskiplog nnskip = 1It was verified to discriminate. With the old
(cur − prev)computation reinstated it printsrelative change still 0.000e+00and three assertions fail.Verification
🤖 Generated with Claude Code