helixiser/bessel_utils.rs
1//! # collection of bessel functions
2//!
3//! The approaches to compute the functions below are probably not optimal.
4//! I would be hesitant to recommend these these functions for your own project.
5
6extern crate num;
7
8// complex numbers
9use num::traits::{Pow};
10use std::f64::consts::PI;
11
12/// Computes J0(x) ( bessel function of the first kind of zeroth order )
13///
14/// Splits the calculations into two regimes where different approximations hold
15/// for the validity of the calculation, see http://www.mhtlab.uwaterloo.ca/courses/me755/web_chap4.pdf
16pub fn J0(x: f64) -> f64 {
17 let mut z = x;
18 if x < 0.0 { z = -x } // we ensure z = |x|, as J0(x) = J0(-x) anyway
19 if z < 2.0 { // in domain x{0,5} we use a simple series expansion
20 return J0a(x)
21 }
22
23 // else we are domain x{2, inf}, where we use a different approximation
24 return J0b(x)
25}
26
27
28/// Computes J1(x) ( bessel function of the first kind of first order )
29///
30/// Splits the calculations into two regimes where different approximations hold
31/// for the validity of the calculation, see http://www.mhtlab.uwaterloo.ca/courses/me755/web_chap4.pdf
32pub fn J1(x: f64) -> f64 {
33 let mut z = x;
34 let mut sign = 1.;
35 if x < 0.0 {
36 z = -x;
37 sign = -1.;
38 } // we ensure z = |x|, as J1(x) = -J1(-x) anyway
39 if z < 2.0 { // in domain x{0,2} we use a simple series expansion
40 return sign * J1a(x)
41 }
42
43 // else we are domain x{2, inf}, where we use a different approximation
44 return sign * J1b(x)
45}
46
47/// Computes J0(x), for small values of x
48pub fn J0a(x: f64) -> f64 {
49 1. - x.pow(2) / 4. + x.pow(4) / 64. - x.pow(6) / (2304.) + x.pow(8) / 147456. -
50 x.pow(10) / 14745600. + x.pow(12) / 2123366400. - x.pow(14) / 416179814400.
51}
52
53
54/// Computes J0(x), for large values of x
55///
56/// for 'large' values of x we use approximations described in:
57/// Harrison, John. "Fast and accurate Bessel function computation."
58/// 2009 19th IEEE Symposium on Computer Arithmetic. IEEE, 2009.
59pub fn J0b(x: f64) -> f64 {
60 let trigarg: f64 = x - PI / 4. - 1. / (8. * x) + 25. / (384. * x.pow(3));
61 (2. / (PI * x)).sqrt() * (1. - (4. * x).pow(-2) + 53. / (512. * x.pow(4))) * trigarg.cos()
62}
63
64/// Computes J1(x), for small values of x
65pub fn J1a(x: f64) -> f64 {
66 x / 2. - x.pow(3) / 16. + x.pow(5) / 384. - x.pow(7) / 18432.
67}
68
69/// Computes J1(x), for large values of x
70///
71/// for 'large' values of x we use approximations described in:
72/// Harrison, John. "Fast and accurate Bessel function computation."
73/// 2009 19th IEEE Symposium on Computer Arithmetic. IEEE, 2009.
74pub fn J1b(x: f64) -> f64 {
75 let trigarg: f64 = x - 3. * PI / 4. + 3. / (8. * x) - 21. / (128. * x.pow(3));
76 (2. / (PI * x)).sqrt() * (1. + 3. * (4. * x).pow(-2) - 99. / (512. * x.pow(4))) * trigarg.cos()
77}
78
79
80/// computes the next bessel "Jn(x)" using the values of Jn_minus_one(x) and Jn_minus_two(x) (at same x)
81///
82/// this makes use of the recurrence relation Jn(x) = 2*(n-1) / x * Jn_minus_one(x) - Jn_minus_two(x)
83/// as described in:
84/// Goldstein, M., and R. M. Thaler. "Recurrence techniques for the calculation of Bessel functions."
85/// Mathematics of Computation 13.66 (1959): 102-108.
86pub fn nextBessel(n: u64, x: f64, Jn_minus_one: f64, Jn_minus_two: f64) -> f64 {
87 let mut sign = 1.;
88 let mut z = x;
89
90 if x < 0.0 {
91 sign = -1.;
92 z = -x;
93 };
94
95 if n == 0 { // we have no choice but to actually compute J0
96 return J0(z)
97 } else if n == 1 { // we have no choice but to actually compute J1
98 return J1(z)
99 }
100 // for n=2 and up, we can use our recurrence relation
101 return sign * (2. * ((n - 1) as f64) / z * Jn_minus_one) - Jn_minus_two
102}
103
104/// computes the value of Jn(x) for given n and x
105///
106/// Based on the implementation in the Cephes mathematical library.
107/// See [https://github.com/vks/special-fun/blob/master/cephes-double/jn.c](https://github.com/vks/special-fun/blob/master/cephes-double/jn.c)
108/// for the original implementation.
109///
110/// # Examples
111/// ```
112/// # use helixiser::bessel_utils::Jn;
113/// # use assert_approx_eq::assert_approx_eq;
114/// // Bessel function of 8th order at x=2
115/// let J8_2 = Jn(8, 2.);
116/// assert_approx_eq!(J8_2, 0.00002217955, 0.000001);
117///
118/// let J3_12 = Jn(3, 12.);
119/// assert_approx_eq!(J3_12, 0.19513693, 0.000001);
120///
121/// let J15_9dot5 = Jn(15, 9.5);
122/// assert_approx_eq!(J15_9dot5, 0.00247161, 0.000001);
123///
124/// let J30_30 = Jn(30, 30.);
125/// assert_approx_eq!(J30_30, 0.1439358, 0.000001);
126/// ```
127pub fn Jn(n: u64, x: f64) -> f64 {
128 let mut sign = 1.;
129 let mut z: f64 = x;
130 if x < 0.0 {
131 sign = -1.;
132 z = -x;
133 };
134
135 if n == 0 { // we have no choice but to actually compute J0
136 return J0(x);
137 } else if n == 1 { // we have no choice but to actually compute J1
138 return J1(x);
139 }
140
141 let mut k = 53;
142 let mut pk: f64 = 2. * (n as f64 + k as f64);
143 let mut ans: f64 = pk;
144 let xk: f64 = z * z;
145
146 while k > 0 {
147 pk -= 2.;
148 ans = pk - xk / ans;
149
150 k -= 1;
151 }
152
153 ans = z / ans;
154
155 pk = 1.;
156 let mut pkm1: f64 = 1. / ans;
157 k = n - 1;
158 let mut r: f64 = 2. * (k as f64);
159 let mut pkm2: f64;
160
161 while k > 0 {
162 pkm2 = (pkm1 * r - pk * z) / z;
163 pk = pkm1;
164 pkm1 = pkm2;
165 r -= 2.;
166
167 k -= 1;
168 }
169
170 if pk.abs() > pkm1.abs() {
171 ans = J1(z) / pk;
172 } else {
173 ans = J0(z) / pkm1;
174 }
175 return sign * ans;
176}
177
178/// get x value of the first maximum of Jn(x) for given n.
179///
180/// Uses tabulated values
181/// # Examples
182/// ```
183/// # use helixiser::bessel_utils::bessel_first_max;
184/// # use assert_approx_eq::assert_approx_eq;
185/// let zero_0 = bessel_first_max(0);
186///
187/// assert_approx_eq!(zero_0, 0.);
188///
189/// let zero_2 = bessel_first_max(2);
190///
191/// assert_approx_eq!(zero_2, 3.054, 0.001);
192///
193/// let zero_2 = bessel_first_max(10);
194///
195/// assert_approx_eq!(zero_2, 11.770, 0.001);
196/// ```
197pub fn bessel_first_max(n: u32) -> f64 {
198 let bessel_max_array: Vec<f64> = vec![
199 0.0, // n=0
200 1.8411845631396737,
201 3.054237453259305,
202 4.201189419366311,
203 5.31755394435717,
204 6.4156173973175346, // n=5
205 7.50126727562796,
206 8.577837671919578,
207 9.647422850370246,
208 10.711435164143216,
209 11.770877850893616, // n=10
210 12.82649237913127,
211 13.878844191880322,
212 14.928375584199912,
213 15.975439867036497,
214 17.020324300768433, // n=15
215 18.06326599063402,
216 19.104463207431817,
217 20.144083640890184,
218 21.182270540354093,
219 22.219147365931807, //n=20
220 23.25482136749967,
221 24.289386377801353,
222 25.322925019895663,
223 26.355510471827266,
224 27.387207891944033, //n=25
225 ];
226 if n > (bessel_max_array.len()-1) as u32 {
227 // ideally you might add a function that uses some minimiser and Jn(x) to find a minimum.
228 panic!("Cannot find bessel maximum for this order") // throw an error
229 } else {
230 bessel_max_array[n as usize]
231 }
232}