Coverage Report

Created: 2025-10-10 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.25/src/bessel/y0f.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 7/2025. All rights reserved.
3
 * //
4
 * // Redistribution and use in source and binary forms, with or without modification,
5
 * // are permitted provided that the following conditions are met:
6
 * //
7
 * // 1.  Redistributions of source code must retain the above copyright notice, this
8
 * // list of conditions and the following disclaimer.
9
 * //
10
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11
 * // this list of conditions and the following disclaimer in the documentation
12
 * // and/or other materials provided with the distribution.
13
 * //
14
 * // 3.  Neither the name of the copyright holder nor the names of its
15
 * // contributors may be used to endorse or promote products derived from
16
 * // this software without specific prior written permission.
17
 * //
18
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
use crate::bessel::j0f::{j0f_asympt_alpha, j0f_asympt_beta, j1f_rsqrt};
30
use crate::bessel::trigo_bessel::sin_small;
31
use crate::bessel::y0f_coeffs::{Y0_ZEROS, Y0_ZEROS_VALUES, Y0F_COEFFS};
32
use crate::common::f_fmla;
33
use crate::double_double::DoubleDouble;
34
use crate::logs::fast_logf;
35
use crate::polyeval::{f_polyeval10, f_polyeval18};
36
use crate::rounding::CpuCeil;
37
use crate::sincos_reduce::rem2pif_any;
38
39
/// Bessel of the second kind of order 0 (Y0)
40
///
41
/// Max ULP 0.5
42
0
pub fn f_y0f(x: f32) -> f32 {
43
0
    let ux = x.to_bits();
44
0
    if ux >= 0xffu32 << 23 || ux == 0 {
45
        // |x| == 0, |x| == inf, |x| == NaN, x < 0
46
0
        if ux.wrapping_shl(1) == 0 {
47
0
            return f32::NEG_INFINITY;
48
0
        }
49
50
0
        if x.is_infinite() {
51
0
            if x.is_sign_negative() {
52
0
                return f32::NAN;
53
0
            }
54
0
            return 0.;
55
0
        }
56
57
0
        return x + f32::NAN; // x == NaN
58
0
    }
59
60
0
    let xb = x.to_bits();
61
62
0
    if xb <= 0x4296999au32 {
63
        // x <= 75.3
64
0
        if xb <= 0x40000000u32 {
65
            // x <= 2
66
0
            if xb <= 0x3faccccdu32 {
67
                // x <= 1.35
68
0
                return y0f_near_zero(f32::from_bits(xb));
69
0
            }
70
            // transient zone from 1.35 to 2 have bad behavior for log poly already,
71
            // and not yet good to be easily covered, thus it use its own poly
72
0
            return y0_transient_area(x);
73
0
        }
74
75
0
        return y0f_small_argument_path(f32::from_bits(xb));
76
0
    }
77
78
    // Exceptions:
79
0
    let xb = x.to_bits();
80
0
    if xb == 0x5023e87f {
81
0
        return f32::from_bits(0x28085b2d);
82
0
    } else if xb == 0x48171521 {
83
0
        return f32::from_bits(0x2bd244ba);
84
0
    } else if xb == 0x4398c299 {
85
0
        return f32::from_bits(0x32c730db);
86
0
    } else if xb == 0x7f0e5a38 {
87
0
        return f32::from_bits(0x131f680b);
88
0
    } else if xb == 0x6ef9be45 {
89
0
        return f32::from_bits(0x987d8a8f);
90
0
    }
91
92
0
    y0f_asympt(x)
93
0
}
94
95
/**
96
Generated by SageMath:
97
Evaluates:
98
Y0(x) = 2/pi*(euler_gamma + log(x/2))*J0(x) - sum((-1)^m*(x/2)^(2*m)/(m!)^2*sum(1+1/2 + ... 1/m))
99
expressed as:
100
Y0(x)=log(x)*W0(x) - Z0(x)
101
```python
102
from sage.all import *
103
104
R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
105
x = R.gen()
106
N = 10  # Number of terms (adjust as needed)
107
gamma = RealField(300)(euler_gamma)
108
d2 = RealField(300)(2)
109
pi = RealField(300).pi()
110
111
# Define J0(x) Taylor expansion at x = 0
112
def j_series(n, x):
113
    return sum([(-1)**m * (x/2)**(ZZ(n) + ZZ(2)*ZZ(m)) / (ZZ(m).factorial() * (ZZ(m) + ZZ(n)).factorial()) for m in range(N)])
114
115
J0_series = j_series(0, x)
116
117
def z_series(x):
118
    return sum([(-1)**m * (x/2)**(ZZ(2)*ZZ(m)) / ZZ(m).factorial()**ZZ(2) * sum(RealField(300)(1)/RealField(300)(k) for k in range(1, m+1)) for m in range(1, N)])
119
120
W0 = (d2/pi) * J0_series
121
Z0 = -gamma * (d2/pi) * J0_series + RealField(300)(2).log() * (d2/pi) * J0_series + (d2/pi) * z_series(x)
122
123
# see the series
124
print(W0)
125
print(Z0)
126
```
127
**/
128
#[inline]
129
0
fn y0f_near_zero(x: f32) -> f32 {
130
    const W: [u64; 10] = [
131
        0x3fe45f306dc9c883,
132
        0xbfc45f306dc9c883,
133
        0x3f845f306dc9c883,
134
        0xbf321bb945252402,
135
        0x3ed21bb945252402,
136
        0xbe672db9f21b0f5f,
137
        0x3df49a6c656d62ff,
138
        0xbd7ae90af76a4d0f,
139
        0x3cfae90af76a4d0f,
140
        0xbc754331c053fdad,
141
    ];
142
0
    let dx = x as f64;
143
0
    let x2 = dx * dx;
144
0
    let w0 = f_polyeval10(
145
0
        x2,
146
0
        f64::from_bits(W[0]),
147
0
        f64::from_bits(W[1]),
148
0
        f64::from_bits(W[2]),
149
0
        f64::from_bits(W[3]),
150
0
        f64::from_bits(W[4]),
151
0
        f64::from_bits(W[5]),
152
0
        f64::from_bits(W[6]),
153
0
        f64::from_bits(W[7]),
154
0
        f64::from_bits(W[8]),
155
0
        f64::from_bits(W[9]),
156
    );
157
    const Z: [u64; 10] = [
158
        0x3fb2e4d699cbd01f,
159
        0xbfc6bbcb41034286,
160
        0x3f9075b1bbf41364,
161
        0xbf41a6206b7b973d,
162
        0x3ee3e99794203bbd,
163
        0xbe7bce4a600d3ea4,
164
        0x3e0a6ee796b871b6,
165
        0xbd92393d82c6b2e4,
166
        0x3d131085da82054c,
167
        0xbc8f4ed4b492ebcc,
168
    ];
169
0
    let z0 = f_polyeval10(
170
0
        x2,
171
0
        f64::from_bits(Z[0]),
172
0
        f64::from_bits(Z[1]),
173
0
        f64::from_bits(Z[2]),
174
0
        f64::from_bits(Z[3]),
175
0
        f64::from_bits(Z[4]),
176
0
        f64::from_bits(Z[5]),
177
0
        f64::from_bits(Z[6]),
178
0
        f64::from_bits(Z[7]),
179
0
        f64::from_bits(Z[8]),
180
0
        f64::from_bits(Z[9]),
181
    );
182
0
    let w_log = fast_logf(x);
183
0
    f_fmla(w0, w_log, -z0) as f32
184
0
}
185
186
#[inline]
187
0
fn y0_transient_area(x: f32) -> f32 {
188
0
    let dx = x as f64;
189
    // first Y0 bessel zero
190
    const ZERO: DoubleDouble =
191
        DoubleDouble::from_bit_pair((0xbc8bd1e50d219bfd, 0x400193bed4dff243));
192
0
    let r = (dx - ZERO.hi) - ZERO.lo;
193
    /*
194
        Poly generated by Wolfram Matematica:
195
        <<FunctionApproximations`
196
        ClearAll["Global`*"]
197
        f[x_]:= BesselY[0,x + 2.1971413260310170351490335626990]
198
        {approx,error} = MiniMaxApproximation[f[x],{x,{ 1.35 - 2.1971413260310170351490335626990, 2 - 2.1971413260310170351490335626990 },17,0},WorkingPrecision->120]
199
        poly=error[[1]];
200
        coeffs=CoefficientList[poly,x];
201
        TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
202
    */
203
0
    let p = f_polyeval18(
204
0
        r,
205
0
        f64::from_bits(0x3fe0aa48442f8375),
206
0
        f64::from_bits(0x3de601d3b959b8d8),
207
0
        f64::from_bits(0xbfd0aa4840bb8529),
208
0
        f64::from_bits(0x3fa439fc16d4835e),
209
0
        f64::from_bits(0x3f80d2dcd97d2b4f),
210
0
        f64::from_bits(0x3f4f833368f9f047),
211
0
        f64::from_bits(0xbf541a702ee92277),
212
0
        f64::from_bits(0x3f3abc113cf0f4da),
213
0
        f64::from_bits(0xbefac1ded6f17ba8),
214
0
        f64::from_bits(0x3f33ef372e24df82),
215
0
        f64::from_bits(0x3f3bf8b42322df40),
216
0
        f64::from_bits(0x3f4582f9daec9ca7),
217
0
        f64::from_bits(0x3f479fc07175494e),
218
0
        f64::from_bits(0x3f4477a5e32b723a),
219
0
        f64::from_bits(0x3f39fbfd6a6d6f0c),
220
0
        f64::from_bits(0x3f2760a66816527b),
221
0
        f64::from_bits(0x3f0a68fdeeba224f),
222
0
        f64::from_bits(0x3edd78c6c87089e1),
223
    );
224
0
    p as f32
225
0
}
226
227
/// This method on small range searches for nearest zero or extremum.
228
/// Then picks stored series expansion at the point end evaluates the poly at the point.
229
#[inline]
230
0
fn y0f_small_argument_path(x: f32) -> f32 {
231
0
    let x_abs = x as f64;
232
233
    // let avg_step = 74.607799 / 47.0;
234
    // let inv_step = 1.0 / avg_step;
235
236
    const INV_STEP: f64 = 0.6299609508652038;
237
238
0
    let fx = x_abs * INV_STEP;
239
    const Y0_ZEROS_COUNT: f64 = (Y0_ZEROS.len() - 1) as f64;
240
0
    let idx0 = unsafe { fx.min(Y0_ZEROS_COUNT).to_int_unchecked::<usize>() };
241
0
    let idx1 = unsafe {
242
0
        fx.cpu_ceil()
243
0
            .min(Y0_ZEROS_COUNT)
244
0
            .to_int_unchecked::<usize>()
245
    };
246
247
0
    let found_zero0 = DoubleDouble::from_bit_pair(Y0_ZEROS[idx0]);
248
0
    let found_zero1 = DoubleDouble::from_bit_pair(Y0_ZEROS[idx1]);
249
250
0
    let dist0 = (found_zero0.hi - x_abs).abs();
251
0
    let dist1 = (found_zero1.hi - x_abs).abs();
252
253
0
    let (found_zero, idx, dist) = if dist0 < dist1 {
254
0
        (found_zero0, idx0, dist0)
255
    } else {
256
0
        (found_zero1, idx1, dist1)
257
    };
258
259
0
    if idx == 0 {
260
        // Really should not happen here, but if it is then to log expansion
261
0
        return y0f_near_zero(x);
262
0
    }
263
264
    // We hit exact zero, value, better to return it directly
265
0
    if dist == 0. {
266
0
        return f64::from_bits(Y0_ZEROS_VALUES[idx]) as f32;
267
0
    }
268
269
0
    let c = &Y0F_COEFFS[idx - 1];
270
271
0
    let r = (x_abs - found_zero.hi) - found_zero.lo;
272
273
0
    let p = f_polyeval18(
274
0
        r,
275
0
        f64::from_bits(c[0]),
276
0
        f64::from_bits(c[1]),
277
0
        f64::from_bits(c[2]),
278
0
        f64::from_bits(c[3]),
279
0
        f64::from_bits(c[4]),
280
0
        f64::from_bits(c[5]),
281
0
        f64::from_bits(c[6]),
282
0
        f64::from_bits(c[7]),
283
0
        f64::from_bits(c[8]),
284
0
        f64::from_bits(c[9]),
285
0
        f64::from_bits(c[10]),
286
0
        f64::from_bits(c[11]),
287
0
        f64::from_bits(c[12]),
288
0
        f64::from_bits(c[13]),
289
0
        f64::from_bits(c[14]),
290
0
        f64::from_bits(c[15]),
291
0
        f64::from_bits(c[16]),
292
0
        f64::from_bits(c[17]),
293
    );
294
295
0
    p as f32
296
0
}
297
298
/*
299
   Evaluates:
300
   Y0 = sqrt(2/(PI*x)) * beta(x) * sin(x - PI/4 - alpha(x))
301
*/
302
#[inline]
303
0
fn y0f_asympt(x: f32) -> f32 {
304
0
    let dx = x as f64;
305
306
0
    let alpha = j0f_asympt_alpha(dx);
307
0
    let beta = j0f_asympt_beta(dx);
308
309
0
    let angle = rem2pif_any(x);
310
311
    const SQRT_2_OVER_PI: f64 = f64::from_bits(0x3fe9884533d43651);
312
    const MPI_OVER_4: f64 = f64::from_bits(0xbfe921fb54442d18);
313
314
0
    let x0pi34 = MPI_OVER_4 - alpha;
315
0
    let r0 = angle + x0pi34;
316
317
0
    let m_cos = sin_small(r0);
318
319
0
    let z0 = beta * m_cos;
320
0
    let scale = SQRT_2_OVER_PI * j1f_rsqrt(dx);
321
322
0
    (scale * z0) as f32
323
0
}
324
325
#[cfg(test)]
326
mod tests {
327
    use crate::f_y0f;
328
329
    #[test]
330
    fn test_y0f() {
331
        assert_eq!(f_y0f(90.5), 0.08254846);
332
        assert_eq!(f_y0f(77.5), 0.087678276);
333
        assert_eq!(f_y0f(1.5), 0.3824489);
334
        assert_eq!(f_y0f(0.5), -0.44451874);
335
        assert!(f_y0f(-1.).is_nan());
336
        assert_eq!(f_y0f(0.), f32::NEG_INFINITY);
337
        assert_eq!(f_y0f(-0.), f32::NEG_INFINITY);
338
        assert_eq!(f_y0f(f32::INFINITY), 0.);
339
        assert!(f_y0f(f32::NEG_INFINITY).is_nan());
340
    }
341
}