Form the Jacobi theta exponents without amplified rounding - #1469
Open
evanmiller wants to merge 2 commits into
Open
evanmiller wants to merge 2 commits into
evanmiller wants to merge 2 commits into
Conversation
Every term of the theta series is exp(-E) where E is a product or quotient of tau, pi and a small integer, or (for tau < 1, after the modular transformation) the square of z plus a multiple of pi/2, divided by pi*tau. Rounding E to working precision costs |E| ulps in the term, and the exponents run into the hundreds before the terms underflow, so the functions lost tens to hundreds of ulps wherever the exponents were large: near tau = 1 in the direct series, and for small tau throughout, where the argument reduction by a rounded pi added an absolute error that the Gaussians amplified by 2z/(pi*tau). The exponents are now formed by a small object that holds pi and a = pi*tau (or -ln q) as unevaluated sums hi + lo and tracks the rounding of every product, sum and quotient with Dekker's and Knuth's error-free transformations, which work for any binary floating-point type (an fma is used where the standard library advertises a fast one). The error is applied to each term as a factor (1 - dE). The argument reduction uses the same split pi and passes the remainder's error into the Gaussians, and the z = 0 shortcuts form the nome of 1/tau without rounding the reciprocal. The q parameterization builds a = -ln q directly instead of going through tau = -ln(q)/pi and back, halving its remaining error. Against a 50-digit reference (double, z in [0, 5], tau in [0.005, 31]): tau parameterization, tau < 1: worst 177-360 ulp -> 3-6, mean 6-12 -> 0.5 tau parameterization, tau >= 1: worst 20-76 ulp -> 2.5, mean 2-8 -> 0.5 q parameterization, q > e^-pi: worst 380-790 ulp -> 64-81, mean 12-14 -> 3 What remains in the q parameterization is the rounding of ln q, which is inherent to it. The "minus one" functions below tau = 1 still cancel against 1, as documented. The Gaussian-sum paths cost about twice what they did; the direct series about a quarter more. The periodicity test shifts z by the rounded two_pi, which for large q now changes the function by more than the evaluation error, so its tolerance accounts for that; the Mellin transform integrals get a couple more ulps of slack. The q sweep of the ULP plot generator started at q = 0, which has been a domain error since the last round of fixes. The ULP graphs are regenerated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
|
@evanmiller , @claude : Could you drop the ULPs plot you created here? Also, please check the committed SVG size-when I wrote the Also, as to cost there are google benchmarks-can we add one and then put it in |
Add cases to the Jacobi theta benchmark that exercise each evaluation path (the Gaussian sums for tau < 1, the z = 0 shortcut, the direct series) at representative points, for the tau and q parameterizations, in float, double and cpp_bin_float_50. The existing theta1 cases draw q from (0, 0.01), which only exercises the direct series in q. Guard the float128 cases so the benchmark builds where quadmath is unavailable. Building it with mpfr types showed that two of the new exponent helpers were called with expression templates rather than numbers, which fails to deduce; convert explicitly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
Author
Contributor
Author
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.






Follow-up to #1468, independent of it.
Every term of the theta series is
exp(-E), whereEis a product or quotient oftau,piand a small integer, or (fortau < 1, after the modular transformation) the square ofzplus a multiple ofpi/2, divided bypi*tau. RoundingEto working precision costs|E|ulps in the term, and the exponents run into the hundreds before the terms underflow. So the functions lost tens to hundreds of ulps wherever the exponents were large: neartau = 1in the direct series, and for smalltauthroughout, where the argument reduction by a roundedpiadded an absolute error that the Gaussians then amplified by2z/(pi*tau).What changed
_jacobi_theta_exponents) holdspianda = pi*tau(or-ln q) as unevaluated sumshi + loand forms every exponent while tracking the rounding of each product, sum and quotient with Dekker's and Knuth's error-free transformations. These work for any binary floating-point type; where<cmath>advertises a fast fma (FP_FAST_FMAetc.) it is used instead. The error is applied to each term as a factor(1 - dE).tau < 1uses the same splitpiand passes the remainder's error into the Gaussian terms.z = 0shortcuts form the nome of1/tauwithout rounding the reciprocal.qparameterization buildsa = -ln qdirectly instead of going throughtau = -ln(q)/piand back, which halves its remaining error.Results
Against a 50-digit reference (double,
zin [0, 5],tauin [0.005, 31]):tauparameterization,tau < 1tauparameterization,tau >= 1qparameterization,q > exp(-pi)What remains in the
qparameterization is the rounding ofln qitself, which is inherent to it. The "minus one" functions belowtau = 1still cancel against 1, as documented.For the Kolmogorov-Smirnov complement CDF, which calls
jacobi_theta4m1tauwith exponents up to 2x²n, theta's own contribution to the error drops from 70 ulp worst (10 mean) to 1.9 (0.45).Cost
The Gaussian-sum paths (
tau < 1,z != 0) take about twice as long as before; the direct series about a quarter more (a few nanoseconds of fixed overhead per call).Tests and docs
The periodicity test shifts
zby the roundedtwo_pi; with an exact reduction that is a genuinely different argument, and for largeqthe functions are steep enough that it changes them by more than the evaluation error, so the test's tolerance now accounts for it (100 eps + 4 pi² eps / -ln q). The Mellin transform integrals, which sum thousands of theta values, get a couple more ulps of slack.test_jacobi_thetaandtest_kolmogorov_smirnovpass.The docs' accuracy notes are updated and the eight ULP graphs regenerated; the
qsweep of the plot generator started atq = 0, which has been a domain error since the previous round of fixes, so it now starts at 1e-6. (The generator also traps when built at-O2with this clang, with the old header as well; I generated the graphs at-O0.)🤖 Generated with Claude Code