/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 | | } |