Skip to content

Fix recurrence order in dawson's exponential-sum branch - #50

Open
gaoflow wants to merge 1 commit into
Axect:masterfrom
gaoflow:fix-dawson-recurrence-order
Open

Fix recurrence order in dawson's exponential-sum branch#50
gaoflow wants to merge 1 commit into
Axect:masterfrom
gaoflow:fix-dawson-recurrence-order

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 28, 2026

Copy link
Copy Markdown

Summary

dawson(x) returns wrong values for every |x| >= 0.2, and faddeeva(x, y)'s
imaginary part is wrong near the real axis. Both come from one bug in the
exponential-sum branch of dawson.

Root cause

src/dawson.rs, the |x| >= 0.2 branch, ports this Numerical Recipes loop:

for (i=0;i<NMAX;i++, d1+=2.0, d2-=2.0, e1*=e2)
    sum += c[i]*(e1/d1 + 1.0/(d2*e1));

The increments are in the for post-clause, so they run after each iteration
body. The port moved them to the top of the body, so the first iteration already
uses d1+2, d2-2, e1*e2 instead of the initialised d1, d2, e1. Every
term is shifted one recurrence step, so the whole branch sums the wrong series.

faddeeva inherits this: for |y| < 1e-8*|x| the imaginary part is
im_w_of_x(x) = 2/sqrt(pi) * dawson(x) (src/faddeeva.rs:280), so the
real-axis imaginary part is wrong too. The real part exp(-x^2) is unaffected.

Impact (before), oracle = mpmath sqrt(pi)/2 * exp(-x^2) * erfi(x)

x dawson(x) before mpmath
0.5 -1.00006 0.42444 (wrong sign)
1.0 -0.11212 0.53808
2.0 0.85660 0.30134
10.0 0.08412 0.05025

faddeeva(0.5, 0.0).im was -1.12845 vs 0.47893.

Fix

Move the three recurrence increments after the sum += accumulation, matching
the NR post-clause. No change is needed in faddeeva.rs.

Why it went unnoticed / tests

dawson_test only checked x = 0.14 (the |x| < 0.2 series branch, which was
always correct), and faddeeva_test only samples Re(z) == Im(z), so the
near-real-axis branch that calls dawson was never exercised.

Added differential tests against mpmath (agreement < 6e-8, within the documented
~2e-7 accuracy):

  • dawson series branch |x| < 0.2 (still correct after the change)
  • dawson exponential-sum branch: boundary x = 0.2, sign region, asymptotic
    tail to x = 10, and negatives
  • odd symmetry D(-x) = -D(x) across both branches
  • faddeeva imaginary part on the real axis (y = 0 and y = 1e-12*x), with
    real part exp(-x^2)

I also checked the other NR recurrence loops in the crate (betacf,
erfccheb, the Bessel routines); none share this post-clause pattern, so the
issue is localised to dawson.

The |x| >= 0.2 branch advanced d1, d2 and e1 at the top of the loop,
one step ahead of the Numerical Recipes for-loop post-clause. Every
term was therefore shifted by one recurrence step, so dawson was wrong
for all |x| >= 0.2 (wrong sign around x = 0.5, orders of magnitude off
for large x). faddeeva's imaginary part near the real axis inherited
this, since it delegates to dawson via im_w_of_x.

Advance the recurrence after accumulating each term, matching NR. Add
differential tests against mpmath covering both dawson branches, the
branch boundary and asymptotic tail, and faddeeva on the real axis.
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.

1 participant