Coverage Report

Created: 2026-05-30 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.29/src/bessel/y1f.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::j1f_rsqrt;
30
use crate::bessel::j1f::{j1f_asympt_alpha, j1f_asympt_beta};
31
use crate::bessel::trigo_bessel::cos_small;
32
use crate::bessel::y1f_coeffs::{Y1_ZEROS, Y1_ZEROS_VALUES, Y1F_COEFFS};
33
use crate::common::f_fmla;
34
use crate::double_double::DoubleDouble;
35
use crate::logs::fast_logf;
36
use crate::polyeval::{f_polyeval10, f_polyeval18, f_polyeval19};
37
use crate::rounding::CpuCeil;
38
use crate::sincos_reduce::rem2pif_any;
39
40
/// Bessel of the second kind of order 1 (Y1)
41
///
42
/// Max ULP 0.5
43
0
pub fn f_y1f(x: f32) -> f32 {
44
0
    let ux = x.to_bits();
45
0
    if ux >= 0xffu32 << 23 || ux == 0 {
46
        // |x| == 0, |x| == inf, |x| == NaN, x < 0
47
0
        if ux.wrapping_shl(1) == 0 {
48
            // |x| == 0
49
0
            return f32::NEG_INFINITY;
50
0
        }
51
52
0
        if x.is_infinite() {
53
0
            if x.is_sign_negative() {
54
0
                return f32::NAN;
55
0
            }
56
0
            return 0.;
57
0
        }
58
0
        return x + f32::NAN; // x == NaN
59
0
    }
60
61
0
    let xb = x.to_bits();
62
63
0
    if xb <= 0x424e0000u32 {
64
        // x <= 51.5
65
0
        if xb <= 0x40000000u32 {
66
            // x <= 2
67
0
            if xb <= 0x3fb5c28fu32 {
68
                // x <= 1.42
69
0
                return y1f_near_zero(x);
70
0
            }
71
            // transient zone from 1.42 to 2 have bad behavior for log poly already,
72
            // and not yet good to be easily covered, thus it use its own poly
73
0
            return y1_transient_area(x);
74
0
        }
75
0
        return y1f_small_argument_path(x);
76
0
    }
77
78
    // Exceptions
79
0
    let bx = x.to_bits();
80
0
    if bx == 0x47037a3d {
81
0
        return f32::from_bits(0x2deededb);
82
0
    } else if bx == 0x65ce46e4 {
83
0
        return f32::from_bits(0x9eed85c4);
84
0
    } else if bx == 0x6bf68a7b {
85
0
        return f32::from_bits(0x9dc70a09);
86
0
    } else if bx == 0x76d84625 {
87
0
        return f32::from_bits(0x15d7a68b);
88
0
    } else if bx == 0x7e3dcda0 {
89
0
        return f32::from_bits(0x12b81111);
90
0
    }
91
92
0
    y1f_asympt(x)
93
0
}
94
95
/**
96
Generated by SageMath:
97
Evaluates:
98
y2 = -J1(x)*log(x) + 1/x * (1 - sum((-1)^m*(H(m)+H(m-1))/(2^m*m!*(m-1)!)*x^(2*m))
99
Y1(x) = 2/pi*(-y2(x)+(euler_gamma - log(2))*J1(x))
100
expressed as:
101
Y1(x)=log(x)*W1(x) - Z1(x) - 2/(pi*x)
102
```python
103
from sage.all import *
104
105
R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
106
x = R.gen()
107
N = 16  # Number of terms (adjust as needed)
108
gamma = RealField(300)(euler_gamma)
109
d2 = RealField(300)(2)
110
pi = RealField(300).pi()
111
log2 = RealField(300)(2).log()
112
113
def j_series(n, x):
114
    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)])
115
116
J1_series = j_series(1, x)
117
118
def harmony(m):
119
    return sum(RealField(300)(1)/RealField(300)(k) for k in range(1, m+1))
120
121
def z_series(x):
122
    return sum([(-1)**m * (x)**(ZZ(2)*ZZ(m)) / (ZZ(2)**(2*m) * ZZ(m).factorial() * (ZZ(m) - ZZ(1)).factorial()) * (harmony(m) + harmony(m - 1)) for m in range(1, N)])
123
124
W1 = d2/pi * J1_series
125
Z1 = -(d2/(x*pi) * z_series(x) + d2/pi * gamma * J1_series(x) - d2/pi * log2 * J1_series(x))
126
127
# see the series
128
print(W0)
129
print(Z0)
130
```
131
See ./notes/bessel_y1_taylor.ipynb for generation
132
**/
133
#[inline]
134
0
fn y1f_near_zero(x: f32) -> f32 {
135
    const W: [u64; 10] = [
136
        0x3fd45f306dc9c883,
137
        0xbfa45f306dc9c883,
138
        0x3f5b2995e7b7b604,
139
        0xbf021bb945252402,
140
        0x3e9cf9286ea1d337,
141
        0xbe2ee7a29824147f,
142
        0x3db78be9987d036d,
143
        0xbd3ae90af76a4d0f,
144
        0x3cb7eb97f85e7d62,
145
        0xbc31028e3376648a,
146
    ];
147
0
    let dx = x as f64;
148
0
    let x2 = dx * dx;
149
0
    let w0 = f_polyeval10(
150
0
        x2,
151
0
        f64::from_bits(W[0]),
152
0
        f64::from_bits(W[1]),
153
0
        f64::from_bits(W[2]),
154
0
        f64::from_bits(W[3]),
155
0
        f64::from_bits(W[4]),
156
0
        f64::from_bits(W[5]),
157
0
        f64::from_bits(W[6]),
158
0
        f64::from_bits(W[7]),
159
0
        f64::from_bits(W[8]),
160
0
        f64::from_bits(W[9]),
161
0
    ) * dx;
162
    const Z: [u64; 10] = [
163
        0x3fc91866143cbc8a,
164
        0xbfabd3975c75b4a7,
165
        0x3f6835b97894be5b,
166
        0xbf12c7dbffcde97d,
167
        0x3eb0a780ac776eac,
168
        0xbe432e5a4ddeea30,
169
        0x3dcf0ce34d2066a6,
170
        0xbd52a4e1aea45c18,
171
        0x3cd1474ade9154ac,
172
        0xbc4978ba84f218c0,
173
    ];
174
0
    let z0 = f_polyeval10(
175
0
        x2,
176
0
        f64::from_bits(Z[0]),
177
0
        f64::from_bits(Z[1]),
178
0
        f64::from_bits(Z[2]),
179
0
        f64::from_bits(Z[3]),
180
0
        f64::from_bits(Z[4]),
181
0
        f64::from_bits(Z[5]),
182
0
        f64::from_bits(Z[6]),
183
0
        f64::from_bits(Z[7]),
184
0
        f64::from_bits(Z[8]),
185
0
        f64::from_bits(Z[9]),
186
0
    ) * dx;
187
0
    let w_log = fast_logf(x);
188
    const TWO_OVER_PI: f64 = f64::from_bits(0x3fe45f306dc9c883);
189
0
    let recip = 1. / dx;
190
0
    let z = f_fmla(w0, w_log, -z0);
191
0
    f_fmla(recip, -TWO_OVER_PI, z) as f32
192
0
}
193
194
#[inline]
195
0
fn y1_transient_area(x: f32) -> f32 {
196
0
    let dx = x as f64;
197
    // first Y0 bessel zero
198
    const ZERO: DoubleDouble =
199
        DoubleDouble::from_bit_pair((0xbc8bd1e50d219bfd, 0x400193bed4dff243));
200
0
    let r = (dx - ZERO.hi) - ZERO.lo;
201
    /*
202
        Poly generated by Wolfram Matematica:
203
        <<FunctionApproximations`
204
        ClearAll["Global`*"]
205
        f[x_]:= BesselY[1,x + 2.1971413260310170351490335626990]
206
        {approx,error} = MiniMaxApproximation[f[x],{x,{1.42 - 2.1971413260310170351490335626990, 2 - 2.1971413260310170351490335626990 },17,0},WorkingPrecision->120]
207
        poly=error[[1]];
208
        coeffs=CoefficientList[poly,x];
209
        TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
210
    */
211
0
    let p = f_polyeval18(
212
0
        r,
213
0
        f64::from_bits(0x3d9b15a8283b069b),
214
0
        f64::from_bits(0x3fe0aa484455fd09),
215
0
        f64::from_bits(0xbfbe56f80802fa38),
216
0
        f64::from_bits(0xbfa0d2ac9d0409ad),
217
0
        f64::from_bits(0xbf73a619b3551650),
218
0
        f64::from_bits(0x3f7e6c480057ecbb),
219
0
        f64::from_bits(0xbf650dc773a5df4d),
220
0
        f64::from_bits(0x3f531e9ccab7d4da),
221
0
        f64::from_bits(0xbf29b76999169b0e),
222
0
        f64::from_bits(0x3f509c829abceaf7),
223
0
        f64::from_bits(0x3f575aee5697c4d8),
224
0
        f64::from_bits(0x3f63f7f9598be176),
225
0
        f64::from_bits(0x3f67a6ae61541282),
226
0
        f64::from_bits(0x3f665e6d3de19021),
227
0
        f64::from_bits(0x3f5ee8837b9197f6),
228
0
        f64::from_bits(0x3f4e6924f270fd7e),
229
0
        f64::from_bits(0x3f32ca61e5b74925),
230
0
        f64::from_bits(0x3f0725735bc3890b),
231
    );
232
0
    p as f32
233
0
}
234
235
/// This method on small range searches for nearest zero or extremum.
236
/// Then picks stored series expansion at the point end evaluates the poly at the point.
237
#[inline]
238
0
fn y1f_small_argument_path(x: f32) -> f32 {
239
0
    let x_abs = x as f64;
240
241
    // let avg_step = 51.03 / 33.0;
242
    // let inv_step = 1.0 / avg_step;
243
    //
244
    // println!("inv_step {}", inv_step);
245
246
    const INV_STEP: f64 = 0.6466784244562023;
247
248
0
    let fx = x_abs * INV_STEP;
249
    const Y1_ZEROS_COUNT: f64 = (Y1_ZEROS.len() - 1) as f64;
250
0
    let idx0 = unsafe { fx.min(Y1_ZEROS_COUNT).to_int_unchecked::<usize>() };
251
0
    let idx1 = unsafe {
252
0
        fx.cpu_ceil()
253
0
            .min(Y1_ZEROS_COUNT)
254
0
            .to_int_unchecked::<usize>()
255
    };
256
257
0
    let found_zero0 = DoubleDouble::from_bit_pair(Y1_ZEROS[idx0]);
258
0
    let found_zero1 = DoubleDouble::from_bit_pair(Y1_ZEROS[idx1]);
259
260
0
    let dist0 = (found_zero0.hi - x_abs).abs();
261
0
    let dist1 = (found_zero1.hi - x_abs).abs();
262
263
0
    let (found_zero, idx, dist) = if dist0 < dist1 {
264
0
        (found_zero0, idx0, dist0)
265
    } else {
266
0
        (found_zero1, idx1, dist1)
267
    };
268
269
0
    if idx == 0 {
270
        // Really should not happen here, but if it is then to log expansion
271
0
        return y1f_near_zero(x);
272
0
    }
273
274
    // We hit exact zero, value, better to return it directly
275
0
    if dist == 0. {
276
0
        return f64::from_bits(Y1_ZEROS_VALUES[idx]) as f32;
277
0
    }
278
279
0
    let c = &Y1F_COEFFS[idx - 1];
280
281
0
    let r = (x_abs - found_zero.hi) - found_zero.lo;
282
283
0
    let p = f_polyeval19(
284
0
        r,
285
0
        f64::from_bits(c[0]),
286
0
        f64::from_bits(c[1]),
287
0
        f64::from_bits(c[2]),
288
0
        f64::from_bits(c[3]),
289
0
        f64::from_bits(c[4]),
290
0
        f64::from_bits(c[5]),
291
0
        f64::from_bits(c[6]),
292
0
        f64::from_bits(c[7]),
293
0
        f64::from_bits(c[8]),
294
0
        f64::from_bits(c[9]),
295
0
        f64::from_bits(c[10]),
296
0
        f64::from_bits(c[11]),
297
0
        f64::from_bits(c[12]),
298
0
        f64::from_bits(c[13]),
299
0
        f64::from_bits(c[14]),
300
0
        f64::from_bits(c[15]),
301
0
        f64::from_bits(c[16]),
302
0
        f64::from_bits(c[17]),
303
0
        f64::from_bits(c[18]),
304
    );
305
306
0
    p as f32
307
0
}
308
309
/*
310
   Evaluates:
311
   Y1 = sqrt(2/(PI*x)) * beta(x) * sin(x - 3*PI/4 - alpha(x))
312
313
   Discarding 1/2*PI gives:
314
   Y1 = sqrt(2/(PI*x)) * beta(x) * (-cos(x - PI/4 - alpha(x)))
315
*/
316
#[inline]
317
0
fn y1f_asympt(x: f32) -> f32 {
318
0
    let dx = x as f64;
319
320
0
    let alpha = j1f_asympt_alpha(dx);
321
0
    let beta = j1f_asympt_beta(dx);
322
323
0
    let angle = rem2pif_any(x);
324
325
    const SQRT_2_OVER_PI: f64 = f64::from_bits(0x3fe9884533d43651);
326
    const MPI_OVER_4: f64 = f64::from_bits(0xbfe921fb54442d18);
327
328
0
    let x0pi34 = MPI_OVER_4 - alpha;
329
0
    let r0 = angle + x0pi34;
330
331
0
    let m_cos = -cos_small(r0);
332
333
0
    let z0 = beta * m_cos;
334
0
    let scale = SQRT_2_OVER_PI * j1f_rsqrt(dx);
335
336
0
    (scale * z0) as f32
337
0
}
338
339
#[cfg(test)]
340
mod tests {
341
    use super::*;
342
343
    #[test]
344
    fn test_bessel_zero() {
345
        assert_eq!(f_y1f(700.76), 0.024876066);
346
        assert_eq!(f_y1f(35.76), 0.121432826);
347
        assert_eq!(f_y1f(1.76), -0.24787569);
348
        assert_eq!(f_y1f(0.87), -0.9030042);
349
        assert_eq!(f_y1f(f32::INFINITY), 0.0);
350
        assert!(f_y1f(f32::NEG_INFINITY).is_nan());
351
        assert!(f_y1f(f32::NAN).is_nan());
352
    }
353
}