1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
//! The Faddeeva function `$w(z) = e^{-z^2}\operatorname{erfc}(-iz)$`.
//!
//! # Algorithm
//!
//! Weideman's rational approximation (*Computation of the complex error function*,
//! SIAM J. Numer. Anal. **31**(5), 1497-1518, 1994). For `$\operatorname{Im} z \ge 0$`,
//! with `$L = 2^{-1/4}\sqrt{N}$`:
//!
//! ```math
//! Z = \frac{L + iz}{L - iz}, \qquad
//! w(z) \approx \frac{1}{\sqrt{\pi}\,(L - iz)} + \frac{2\,P(Z)}{(L - iz)^2}
//! ```
//!
//! where `$P$` has degree `$N-1$` and **real** coefficients. This is the only method in
//! the literature that is a single branch-free closed form over the whole upper
//! half-plane, and that is why it was chosen:
//!
//! - No region split. Every competing algorithm of comparable accuracy (Humlicek's w4,
//! Zaghloul's Algorithm 916/985, Poppe & Wijers' Algorithm 680, Al Azah &
//! Chandler-Wilde's modified trapezoidal rules) partitions the plane, which under
//! SIMD costs the sum of every region rather than the one that applies.
//! - **One real reciprocal.** `$L - iz = (L + y) - ix$`, so `$|L - iz| \ge L + y \ge L$`
//! and the inversion needs no guard. See [`weideman`] for the single-reciprocal form.
//! - No transcendentals at all in the upper half-plane: no `exp`, `ln`, or `sincos`.
//! - `$|Z| \le 1$` for `$\operatorname{Im} z \ge 0$`, so the polynomial argument is
//! confined to the closed unit disk and the Horner recurrence is well conditioned.
//! The only pole is at `$z = -iL$`, in the lower half-plane, and the reflection
//! evaluates at `$-z$`, so it is never approached.
//!
//! # Accuracy
//!
//! Measured against a 50-digit oracle over a grid covering `$|z|$` from `1e-8` to `1e5`
//! and `$\operatorname{Im} z$` from `0` to `30`:
//!
//! | N | 8 | 16 | 24 | 32 | 40 |
//! |---|---|----|----|----|----|
//! | max relative error | 3.1e-4 | 4.3e-7 | 4.2e-10 | 3.1e-13 | 8.7e-16 |
//!
//! Convergence is geometric, about `$10^{-0.385 N}$`. `N = 40` reaches the binary64
//! roundoff floor; `N = 48` measures no better (6.1e-16 against 8.7e-16 - both are the
//! floor, not convergence), so 40 ends the ladder and [`weideman_n`] never exceeds it.
//! f32 floors at `N = 16` (~5 ulp) for the same reason.
//!
//! This error is **normwise**, relative to `$|w|$`, and uniform over the upper
//! half-plane: it does not degrade at `$\operatorname{Im} z = 0$`, and it *improves*
//! with large `$|\operatorname{Re} z|$` (2.6e-16 at `x = 1e4`, `N = 32`).
//!
//! The received wisdom that this method "fails near the real axis" is a statement about
//! `$\operatorname{Re} w$` alone, and it is a property of `$w$` rather than of the
//! approximation. On the real axis `$\operatorname{Re} w(x) = e^{-x^2}$` while
//! `$|w| \sim 1/(\sqrt{\pi}x)$`, so a uniform normwise error `$\epsilon$` lands as
//! about `$\epsilon\,x/y$` on the real part alone. Consumers of the complex value -
//! `erf`, `erfc`, `erfi`, Dawson - are unaffected. A Voigt profile, which *is*
//! `$\operatorname{Re} w$`, is not, so `real_axis_w` takes that strip over at
//! `Best` and above. It needs no separate Dawson kernel: on the axis
//! `$\operatorname{Im} w(x) = \frac{2}{\sqrt{\pi}}F(x)$` *is* Dawson's integral, and it
//! is the component this approximation delivers to full relative accuracy, so the seed
//! is already in hand and only `$\operatorname{Re} w = e^{-x^2}$` has to be restored.
use ;
use ;
use *;
use FloatElement;
use crateComplex;
use crateRealFloatVector;
/// Weideman's coefficients for an `N`-term approximation.
///
/// `A` is stored **leading-term-first**, matching both MATLAB's `polyval` order (which
/// is what the reference `cef.m` produces after its `flipud`) and
/// [`poly_rev`](thermite::math::specialized::SpecializedCoreMath::poly_rev). Feeding it
/// to a constant-term-first evaluator silently produces a different polynomial that
/// happens to agree at `Z = 1`; that exact confusion has already caused one shipped bug
/// in this workspace's Lanczos denominator.
///
/// The values come from the recipe in `reference/cef.m` - sample `$e^{-t^2}(L^2 + t^2)$`
/// on the tangent grid `$t = L\tan(\theta/2)$` at `$4N$` points, take the real FFT, keep
/// and reverse entries `$1..N$`. They are tabulated rather than computed because the FFT
/// is not available at const-eval time.
/// The tiers of [`Weideman`] an element type provides, and where its ladder stops.
/// The term count for a precision tier, clamped to what `max_n` can deliver.
///
/// Each tier is the smallest `N` whose measured error (see the [module docs](self))
/// clears the next format-relevant threshold.
pub const
/// Horner over **real** coefficients at a complex argument, leading-term-first.
///
/// [`poly_rev`](thermite::math::specialized::SpecializedCoreMath::poly_rev) cannot serve
/// here: its coefficients are `Self::Element`, which for `Complex<V>` is `Complex<E>`,
/// so it would carry `N` known-zero imaginary parts through the whole chain - a wasted
/// add per term and twice the table. The iteration order matches `poly_rev`'s.
///
/// `N` is a literal at every call site (the ladder in [`faddeeva_w`] instantiates it as
/// one of 8/16/24/32/40), which is what lets the trip count and the coefficient loads
/// fold.
/// `w(z)` by the `N`-term Weideman approximation. **Valid only for `Im z >= 0`.**
///
/// Written to need exactly one real reciprocal:
///
/// ```text
/// r = 1/(L - iz) -- Complex::rcp, i.e. conj(d)/|d|^2
/// Z = (L + iz) * r
/// w = (2*P(Z)*r + 1/sqrt(pi)) * r
/// ```
///
/// The factor 2 is applied as `pr + pr` rather than folded into the table, so the
/// coefficients stay byte-identical to `cef.m`'s output for review. Doubling is exact,
/// and a complex add costs the same as a complex scale.
/// [`weideman_with`], taking the table from the element's own [`Weideman`] impl.
/// `w(z)` near the real axis, where the direct evaluation loses the real part.
///
/// `$\operatorname{Re} w$` is not recoverable from a normwise-accurate evaluation there:
/// at `$z = -8$` the true value is `$e^{-64} = 1.6 \times 10^{-28}$` while the noise floor
/// `$\epsilon|w|$` is `$1.6 \times 10^{-17}$`, eleven orders above it. Every intermediate
/// is a normal number - the smallest is 0.07 - so this is lost information, not underflow,
/// and no rearrangement of the same expression recovers it.
///
/// So the real part is taken from the one place it is exact, and the imaginary part from
/// the one place the direct evaluation is reliable:
///
/// ```text
/// a_0 = exp(-x^2) + i*Im w(x, 0)
/// ```
///
/// `$\operatorname{Im} w(x) = \frac{2}{\sqrt{\pi}}F(x)$` is Dawson's integral, and it is
/// *what `$|w|$` is made of* out on the axis - so Weideman delivers it to full relative
/// accuracy (measured 1e-13 at N=32, 6e-16 at N=40 against a 50-digit oracle). No separate
/// Dawson kernel is needed; the value falls out of the kernel already here.
///
/// Stepping off the axis uses `$w'(z) = -2zw + 2i/\sqrt{\pi}$` differentiated `n` times,
/// which gives `$a_{n+1} = -2(x a_n + a_{n-1})/(n+1)$`. Folding `$(iy)^n$` into the terms
/// as `$b_n = a_n (iy)^n$` turns that into a recursion with no complex powers and no
/// divisions:
///
/// ```text
/// b_{n+1} = (-2/(n+1)) * (x*(iy)*b_n - y^2*b_{n-1})
/// ```
///
/// Four terms, and deliberately not more. The series is asymptotic rather than
/// convergent once `$xy$` grows, so outside the gate extra terms make it *worse* - at
/// `$x = 10^6, y = 10^{-5}$`, four give 3e-3 and twelve give 9.0. Inside the gate,
/// terms beyond the fourth contribute nothing measurable.
///
/// # Cost
///
/// A second [`weideman`] evaluation, at `$(x, 0)$`, plus one `exp` and two recursion
/// steps. The second evaluation is not avoidable: seeding from the imaginary part of the
/// `$(x, y)$` evaluation already in hand drifts by about `$0.02y$` relative, which is fine
/// at `$y = 10^{-8}$` and useless by `$y = 10^{-2}$`.
/// `w(z)` over the whole complex plane, with `N` chosen by the precision policy.
///
/// The upper half-plane is [`weideman`] directly. The lower half-plane uses the
/// reflection `$w(z) = 2e^{-z^2} - w(-z)$`, which is where the only two hazards live:
///
/// - `$e^{-z^2}$` overflows for `$\operatorname{Im} z < -\sqrt{\ln(\text{MAX})}$`
/// (about -26.64 in binary64), but `$w$` itself overflows there too, so the infinity
/// is the correct answer and is not guarded.
/// - `$e^{-z^2}$` needs `$\sin/\cos(2xy)$`, and `$2xy$` grows without bound - about
/// `2e5` radians at `$z = 10^4 - 10i$`. This is the one place in the function where
/// argument-reduction error is the *entire* error, the output range being fixed while
/// the argument is not. Accuracy in the lower half-plane at large `$|xy|$` is bounded
/// by the underlying `sincos` reduction, not by `N`.
/// [`faddeeva_w_with`], with `N` and the table chosen by the precision policy.