pxfm/bessel/j0f.rs
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 */
29use crate::bessel::j0f_coeffs::{J0_ZEROS, J0_ZEROS_VALUE, J0F_COEFFS};
30use crate::bessel::trigo_bessel::cos_small;
31use crate::double_double::DoubleDouble;
32use crate::polyeval::{f_polyeval9, f_polyeval10, f_polyeval12, f_polyeval14};
33use crate::rounding::CpuCeil;
34use crate::sincos_reduce::rem2pif_any;
35
36/// Bessel of the first kind of order 0
37///
38/// Max ulp 0.5
39pub fn f_j0f(x: f32) -> f32 {
40 let ux = x.to_bits().wrapping_shl(1);
41 if ux >= 0xffu32 << 24 || ux <= 0x6800_0000u32 {
42 // |x| == 0, |x| == inf, |x| == NaN, |x| <= f32::EPSILON
43 if ux == 0 {
44 // |x| == 0
45 return f64::from_bits(0x3ff0000000000000) as f32;
46 }
47 if x.is_infinite() {
48 return 0.;
49 }
50
51 if ux <= 0x6800_0000u32 {
52 // |x| < f32::EPSILON
53 // taylor series for J0(x) ~ 1 - x^2/4 + O(x^4)
54 #[cfg(any(
55 all(
56 any(target_arch = "x86", target_arch = "x86_64"),
57 target_feature = "fma"
58 ),
59 target_arch = "aarch64"
60 ))]
61 {
62 use crate::common::f_fmlaf;
63 return f_fmlaf(x, -x * 0.25, 1.);
64 }
65 #[cfg(not(any(
66 all(
67 any(target_arch = "x86", target_arch = "x86_64"),
68 target_feature = "fma"
69 ),
70 target_arch = "aarch64"
71 )))]
72 {
73 use crate::common::f_fmla;
74 let dx = x as f64;
75 return f_fmla(dx, -dx * 0.25, 1.) as f32;
76 }
77 }
78
79 return x + f32::NAN; // x == NaN
80 }
81
82 let x_abs = x.to_bits() & 0x7fff_ffff;
83
84 if x_abs <= 0x4295999au32 {
85 // |x| <= 74.8
86 if x_abs <= 0x3e800000u32 {
87 // |x| <= 0.25
88 return j0f_maclaurin_series(x);
89 }
90
91 if x_abs == 0x401a42e8u32 {
92 return f32::from_bits(0xbb3b2f69u32);
93 }
94
95 return small_argument_path(x);
96 }
97
98 // Exceptions
99 if x_abs == 0x65ce46e4 {
100 return f32::from_bits(0x1eed85c4);
101 } else if x_abs == 0x7e3dcda0 {
102 return f32::from_bits(0x92b81111);
103 } else if x_abs == 0x76d84625 {
104 return f32::from_bits(0x95d7a68b);
105 } else if x_abs == 0x6bf68a7b {
106 return f32::from_bits(0x1dc70a09);
107 } else if x_abs == 0x7842c820 {
108 return f32::from_bits(0x17ebf13e);
109 } else if x_abs == 0x4ba332e9 {
110 return f32::from_bits(0x27250206);
111 }
112
113 j0f_asympt(f32::from_bits(x_abs))
114}
115
116/**
117Generated by SageMath:
118```python
119# Maclaurin series for j0
120def print_expansion_at_0_f():
121 print(f"pub(crate) const J0_MACLAURIN_SERIES: [u64; 9] = [")
122 from mpmath import mp, j0, taylor
123 mp.prec = 60
124 poly = taylor(lambda val: j0(val), 0, 18)
125 z = 0
126 for i in range(0, 18, 2):
127 print(f"{double_to_hex(poly[i])},")
128 print("];")
129
130 print(f"poly {poly}")
131
132print_expansion_at_0_f()
133```
134**/
135#[inline]
136fn j0f_maclaurin_series(x: f32) -> f32 {
137 pub(crate) const C: [u64; 9] = [
138 0x3ff0000000000000,
139 0xbfd0000000000000,
140 0x3f90000000000000,
141 0xbf3c71c71c71c71c,
142 0x3edc71c71c71c71c,
143 0xbe723456789abcdf,
144 0x3e002e85c0898b71,
145 0xbd8522a43f65486a,
146 0x3d0522a43f65486a,
147 ];
148 let dx = x as f64;
149 f_polyeval9(
150 dx * dx,
151 f64::from_bits(C[0]),
152 f64::from_bits(C[1]),
153 f64::from_bits(C[2]),
154 f64::from_bits(C[3]),
155 f64::from_bits(C[4]),
156 f64::from_bits(C[5]),
157 f64::from_bits(C[6]),
158 f64::from_bits(C[7]),
159 f64::from_bits(C[8]),
160 ) as f32
161}
162
163/// This method on small range searches for nearest zero or extremum.
164/// Then picks stored series expansion at the point end evaluates the poly at the point.
165#[inline]
166fn small_argument_path(x: f32) -> f32 {
167 let x_abs = f32::from_bits(x.to_bits() & 0x7fff_ffff) as f64;
168
169 // let avg_step = 74.6145 / 47.0;
170 // let inv_step = 1.0 / avg_step;
171
172 const INV_STEP: f64 = 0.6299043751549631;
173
174 let fx = x_abs * INV_STEP;
175 const J0_ZEROS_COUNT: f64 = (J0_ZEROS.len() - 1) as f64;
176 let idx0 = unsafe { fx.min(J0_ZEROS_COUNT).to_int_unchecked::<usize>() };
177 let idx1 = unsafe {
178 fx.cpu_ceil()
179 .min(J0_ZEROS_COUNT)
180 .to_int_unchecked::<usize>()
181 };
182
183 let found_zero0 = DoubleDouble::from_bit_pair(J0_ZEROS[idx0]);
184 let found_zero1 = DoubleDouble::from_bit_pair(J0_ZEROS[idx1]);
185
186 let dist0 = (found_zero0.hi - x_abs).abs();
187 let dist1 = (found_zero1.hi - x_abs).abs();
188
189 let (found_zero, idx, dist) = if dist0 < dist1 {
190 (found_zero0, idx0, dist0)
191 } else {
192 (found_zero1, idx1, dist1)
193 };
194
195 if idx == 0 {
196 return j0f_maclaurin_series(x);
197 }
198
199 // We hit exact zero, value, better to return it directly
200 if dist == 0. {
201 return f64::from_bits(J0_ZEROS_VALUE[idx]) as f32;
202 }
203
204 let c = &J0F_COEFFS[idx - 1];
205
206 let r = (x_abs - found_zero.hi) - found_zero.lo;
207
208 let p = f_polyeval14(
209 r,
210 f64::from_bits(c[0]),
211 f64::from_bits(c[1]),
212 f64::from_bits(c[2]),
213 f64::from_bits(c[3]),
214 f64::from_bits(c[4]),
215 f64::from_bits(c[5]),
216 f64::from_bits(c[6]),
217 f64::from_bits(c[7]),
218 f64::from_bits(c[8]),
219 f64::from_bits(c[9]),
220 f64::from_bits(c[10]),
221 f64::from_bits(c[11]),
222 f64::from_bits(c[12]),
223 f64::from_bits(c[13]),
224 );
225
226 p as f32
227}
228
229#[inline]
230pub(crate) fn j1f_rsqrt(x: f64) -> f64 {
231 (1. / x) * x.sqrt()
232}
233
234/*
235 Evaluates:
236 J1 = sqrt(2/(PI*x)) * beta(x) * cos(x - PI/4 - alpha(x))
237*/
238#[inline]
239fn j0f_asympt(x: f32) -> f32 {
240 let dx = x as f64;
241
242 let alpha = j0f_asympt_alpha(dx);
243 let beta = j0f_asympt_beta(dx);
244
245 let angle = rem2pif_any(x);
246
247 const SQRT_2_OVER_PI: f64 = f64::from_bits(0x3fe9884533d43651);
248 const MPI_OVER_4: f64 = f64::from_bits(0xbfe921fb54442d18);
249
250 let x0pi34 = MPI_OVER_4 - alpha;
251 let r0 = angle + x0pi34;
252
253 let m_cos = cos_small(r0);
254
255 let z0 = beta * m_cos;
256 let scale = SQRT_2_OVER_PI * j1f_rsqrt(dx);
257
258 (scale * z0) as f32
259}
260
261/**
262Note expansion generation below: this is negative series expressed in Sage as positive,
263so before any real evaluation `x=1/x` should be applied.
264
265Generated by SageMath:
266```python
267def binomial_like(n, m):
268 prod = QQ(1)
269 z = QQ(4)*(n**2)
270 for k in range(1,m + 1):
271 prod *= (z - (2*k - 1)**2)
272 return prod / (QQ(2)**(2*m) * (ZZ(m).factorial()))
273
274R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
275x = R.gen()
276
277def Pn_asymptotic(n, y, terms=10):
278 # now y = 1/x
279 return sum( (-1)**m * binomial_like(n, 2*m) / (QQ(2)**(2*m)) * y**(QQ(2)*m) for m in range(terms) )
280
281def Qn_asymptotic(n, y, terms=10):
282 return sum( (-1)**m * binomial_like(n, 2*m + 1) / (QQ(2)**(2*m + 1)) * y**(QQ(2)*m + 1) for m in range(terms) )
283
284P = Pn_asymptotic(0, x, 50)
285Q = Qn_asymptotic(0, x, 50)
286
287R_series = (-Q/P)
288
289# alpha is atan(R_series) so we're doing Taylor series atan expansion on R_series
290
291arctan_series_Z = sum([QQ(-1)**k * x**(QQ(2)*k+1) / RealField(700)(RealField(700)(2)*k+1) for k in range(25)])
292alpha_series = arctan_series_Z(R_series)
293
294# see the series
295print(alpha_series)
296```
297**/
298#[inline]
299pub(crate) fn j0f_asympt_alpha(x: f64) -> f64 {
300 const C: [u64; 12] = [
301 0x3fc0000000000000,
302 0xbfb0aaaaaaaaaaab,
303 0x3fcad33333333333,
304 0xbffa358492492492,
305 0x403779a1f8e38e39,
306 0xc080bd1fc8b1745d,
307 0x40d16b51e66c789e,
308 0xc128ecc3af33ab37,
309 0x418779dae2b8512f,
310 0xc1ec296336955c7f,
311 0x4254f5ee683b6432,
312 0xc2c2f51eced6693f,
313 ];
314 let recip = 1. / x;
315 let x2 = recip * recip;
316 let p = f_polyeval12(
317 x2,
318 f64::from_bits(C[0]),
319 f64::from_bits(C[1]),
320 f64::from_bits(C[2]),
321 f64::from_bits(C[3]),
322 f64::from_bits(C[4]),
323 f64::from_bits(C[5]),
324 f64::from_bits(C[6]),
325 f64::from_bits(C[7]),
326 f64::from_bits(C[8]),
327 f64::from_bits(C[9]),
328 f64::from_bits(C[10]),
329 f64::from_bits(C[11]),
330 );
331 p * recip
332}
333
334/**
335Beta series
336
337Generated by SageMath:
338```python
339#generate b series
340def binomial_like(n, m):
341 prod = QQ(1)
342 z = QQ(4)*(n**2)
343 for k in range(1,m + 1):
344 prod *= (z - (2*k - 1)**2)
345 return prod / (QQ(2)**(2*m) * (ZZ(m).factorial()))
346
347R = LaurentSeriesRing(RealField(300), 'x', default_prec=300)
348x = R.gen()
349
350def Pn_asymptotic(n, y, terms=10):
351 # now y = 1/x
352 return sum( (-1)**m * binomial_like(n, 2*m) / (QQ(2)**(2*m)) * y**(QQ(2)*m) for m in range(terms) )
353
354def Qn_asymptotic(n, y, terms=10):
355 return sum( (-1)**m * binomial_like(n, 2*m + 1) / (QQ(2)**(2*m + 1)) * y**(QQ(2)*m + 1) for m in range(terms) )
356
357P = Pn_asymptotic(0, x, 50)
358Q = Qn_asymptotic(0, x, 50)
359
360def sqrt_series(s):
361 val = S.valuation()
362 lc = S[val] # Leading coefficient
363 b = lc.sqrt() * x**(val // 2)
364
365 for _ in range(5):
366 b = (b + S / b) / 2
367 b = b
368 return b
369
370S = (P**2 + Q**2).truncate(50)
371
372b_series = sqrt_series(S).truncate(30)
373#see the series
374print(b_series)
375```
376**/
377#[inline]
378pub(crate) fn j0f_asympt_beta(x: f64) -> f64 {
379 const C: [u64; 10] = [
380 0x3ff0000000000000,
381 0xbfb0000000000000,
382 0x3fba800000000000,
383 0xbfe15f0000000000,
384 0x4017651180000000,
385 0xc05ab8c13b800000,
386 0x40a730492f262000,
387 0xc0fc73a7acd696f0,
388 0x41577458dd9fce68,
389 0xc1b903ab9b27e18f,
390 ];
391 let recip = 1. / x;
392 let x2 = recip * recip;
393 f_polyeval10(
394 x2,
395 f64::from_bits(C[0]),
396 f64::from_bits(C[1]),
397 f64::from_bits(C[2]),
398 f64::from_bits(C[3]),
399 f64::from_bits(C[4]),
400 f64::from_bits(C[5]),
401 f64::from_bits(C[6]),
402 f64::from_bits(C[7]),
403 f64::from_bits(C[8]),
404 f64::from_bits(C[9]),
405 )
406}
407
408#[cfg(test)]
409mod tests {
410 use crate::f_j0f;
411
412 #[test]
413 fn test_j0f() {
414 println!("0x{:8x}", f32::EPSILON.to_bits().wrapping_shl(1));
415 assert_eq!(f_j0f(-3123.), 0.012329336);
416 assert_eq!(f_j0f(-0.1), 0.99750155);
417 assert_eq!(f_j0f(-15.1), -0.03456193);
418 assert_eq!(f_j0f(3123.), 0.012329336);
419 assert_eq!(f_j0f(0.1), 0.99750155);
420 assert_eq!(f_j0f(15.1), -0.03456193);
421 assert_eq!(f_j0f(f32::INFINITY), 0.);
422 assert_eq!(f_j0f(f32::NEG_INFINITY), 0.);
423 assert!(f_j0f(f32::NAN).is_nan());
424 }
425}