math_audio_wave/special/
spherical.rs1use num_complex::Complex64;
24
25pub fn spherical_bessel_j(order: usize, x: f64) -> Vec<f64> {
46 assert!(order >= 1, "Order must be at least 1");
47
48 let mut result = vec![0.0; order];
49
50 if x.abs() < 1e-15 {
52 result[0] = 1.0;
53 return result;
54 }
55
56 if x.abs() < 1e-10 {
57 result[0] = 1.0 - x * x / 6.0;
59 if order > 1 {
60 result[1] = x / 3.0;
61 }
62 for item in result.iter_mut().take(order).skip(2) {
63 *item = 0.0;
64 }
65 return result;
66 }
67
68 let start_n = order + (x.abs() as usize) + 20;
71
72 let mut values = vec![0.0; start_n + 2];
73 values[start_n + 1] = 0.0;
74 values[start_n] = 1e-30; for k in (0..start_n).rev() {
78 values[k] = (2 * k + 3) as f64 / x * values[k + 1] - values[k + 2];
79 }
80
81 let true_j0 = x.sin() / x;
83 let scale = true_j0 / values[0];
84
85 for n in 0..order {
86 result[n] = values[n] * scale;
87 }
88
89 result
90}
91
92pub fn spherical_bessel_y(order: usize, x: f64) -> Vec<f64> {
109 assert!(order >= 1, "Order must be at least 1");
110
111 let mut result = vec![0.0; order];
112
113 if x.abs() < 1e-15 {
114 for item in result.iter_mut().take(order) {
116 *item = f64::NEG_INFINITY;
117 }
118 return result;
119 }
120
121 let cos_x = x.cos();
122 let sin_x = x.sin();
123
124 result[0] = -cos_x / x;
126
127 if order == 1 {
128 return result;
129 }
130
131 result[1] = -cos_x / (x * x) - sin_x / x;
133
134 for n in 2..order {
136 result[n] = (2 * n - 1) as f64 / x * result[n - 1] - result[n - 2];
137 }
138
139 result
140}
141
142pub fn spherical_hankel_first_kind(order: usize, x: f64, harmonic_factor: f64) -> Vec<Complex64> {
162 assert!(order >= 2, "Order must be at least 2");
163 assert!(x > 0.0, "Argument must be positive");
164
165 let mut result = vec![Complex64::new(0.0, 0.0); order];
166
167 let cos_x = x.cos();
169 let sin_x = x.sin();
170
171 let mut y_n = vec![0.0; order];
172 y_n[0] = -cos_x / x;
173 y_n[1] = -(cos_x / x + sin_x) / x;
174
175 for n in 2..order {
176 y_n[n] = (2 * n - 1) as f64 / x * y_n[n - 1] - y_n[n - 2];
177 }
178
179 let nu = (order - 1) as f64;
182 let eps_gn = 1e-9;
183
184 let mut di = (2.0 * (nu + 1.0) + 1.0) / x;
186 let mut cj = di;
187 let mut dj = 0.0;
188 let mut err_gn = 1.0;
189 let mut j = 1;
190
191 while err_gn > eps_gn {
192 let aj = -1.0;
193 let bj = (2.0 * (nu + j as f64 + 1.0) + 1.0) / x;
194
195 dj = bj + aj * dj;
196 if dj == 0.0 {
197 dj = 1e-30;
198 }
199 dj = 1.0 / dj;
200
201 cj = bj + aj / cj;
202 if cj == 0.0 {
203 cj = 1e-30;
204 }
205
206 di = di * cj * dj;
207 err_gn = (cj * dj - 1.0).abs();
208 j += 1;
209
210 if j > 1000 {
211 break;
213 }
214 }
215
216 let gnu = nu / x - 1.0 / di;
217
218 let mut gg_n = vec![0.0; order];
220 let mut dg_n = vec![0.0; order];
221
222 gg_n[order - 1] = 1.0;
223 dg_n[order - 1] = gnu;
224
225 for i in (0..order - 1).rev() {
226 let di = i as f64;
227 gg_n[i] = (di + 2.0) / x * gg_n[i + 1] + dg_n[i + 1];
228 dg_n[i] = di / x * gg_n[i] - gg_n[i + 1];
229 }
230
231 let dp = if gg_n[0].abs() > 1e-5 {
233 sin_x / x / gg_n[0]
234 } else {
235 (cos_x - sin_x / x) / x / dg_n[0]
236 };
237
238 for n in 0..order {
240 result[n] = Complex64::new(dp * gg_n[n], harmonic_factor * y_n[n]);
241 }
242
243 result
244}
245
246pub fn spherical_bessel_j_derivative(order: usize, x: f64) -> Vec<f64> {
253 let j = spherical_bessel_j(order + 1, x);
254 let mut result = vec![0.0; order];
255
256 for n in 0..order {
257 if n == 0 {
258 result[0] = -j[1];
260 } else {
261 result[n] = j[n - 1] - (n + 1) as f64 / x * j[n];
262 }
263 }
264
265 result
266}
267
268pub fn spherical_bessel_y_derivative(order: usize, x: f64) -> Vec<f64> {
275 let y = spherical_bessel_y(order + 1, x);
276 let mut result = vec![0.0; order];
277
278 for n in 0..order {
279 if n == 0 {
280 result[0] = -y[1];
282 } else {
283 result[n] = y[n - 1] - (n + 1) as f64 / x * y[n];
284 }
285 }
286
287 result
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use std::f64::consts::PI;
294
295 const EPSILON: f64 = 1e-10;
296
297 #[test]
298 fn test_spherical_bessel_j0() {
299 let j = spherical_bessel_j(1, 1.0);
301 let expected = 1.0_f64.sin() / 1.0;
302 assert!((j[0] - expected).abs() < EPSILON);
303
304 let j = spherical_bessel_j(1, PI);
305 let expected = PI.sin() / PI;
306 assert!((j[0] - expected).abs() < EPSILON);
307 }
308
309 #[test]
310 fn test_spherical_bessel_j1() {
311 let x = 2.0;
313 let j = spherical_bessel_j(2, x);
314 let expected = x.sin() / (x * x) - x.cos() / x;
315 assert!((j[1] - expected).abs() < EPSILON);
316 }
317
318 #[test]
319 fn test_spherical_bessel_y0() {
320 let x = 1.0;
322 let y = spherical_bessel_y(1, x);
323 let expected = -x.cos() / x;
324 assert!((y[0] - expected).abs() < EPSILON);
325 }
326
327 #[test]
328 fn test_spherical_bessel_y1() {
329 let x = 2.0;
331 let y = spherical_bessel_y(2, x);
332 let expected = -x.cos() / (x * x) - x.sin() / x;
333 assert!((y[1] - expected).abs() < EPSILON);
334 }
335
336 #[test]
337 fn test_spherical_hankel_consistency() {
338 let x = 3.0;
340 let order = 5;
341 let j = spherical_bessel_j(order, x);
342 let y = spherical_bessel_y(order, x);
343 let h = spherical_hankel_first_kind(order, x, 1.0);
344
345 for n in 0..order {
346 assert!(
347 (h[n].re - j[n]).abs() < 1e-8,
348 "Real part mismatch at n={}: {} vs {}",
349 n,
350 h[n].re,
351 j[n]
352 );
353 assert!(
354 (h[n].im - y[n]).abs() < 1e-8,
355 "Imag part mismatch at n={}: {} vs {}",
356 n,
357 h[n].im,
358 y[n]
359 );
360 }
361 }
362
363 #[test]
364 fn test_hankel_asymptotic() {
365 let x = 50.0;
367 let h = spherical_hankel_first_kind(3, x, 1.0);
368
369 let expected_re = x.sin() / x;
371 let expected_im = -x.cos() / x;
372
373 assert!(
374 (h[0].re - expected_re).abs() < 0.01,
375 "Asymptotic real mismatch"
376 );
377 assert!(
378 (h[0].im - expected_im).abs() < 0.01,
379 "Asymptotic imag mismatch"
380 );
381 }
382
383 #[test]
384 fn test_bessel_derivative_j0() {
385 let x = 2.0;
387 let jp = spherical_bessel_j_derivative(1, x);
388 let j = spherical_bessel_j(2, x);
389 assert!((jp[0] + j[1]).abs() < EPSILON);
390 }
391
392 #[test]
393 fn test_recurrence_stability() {
394 let x = 5.0;
396 let order = 20;
397 let j = spherical_bessel_j(order, x);
398
399 for (n, &jn) in j.iter().enumerate().take(order) {
401 assert!(jn.is_finite(), "j_{} is not finite", n);
402 }
403
404 assert!(j[15].abs() < j[5].abs());
406 }
407}