1use crate::context::truncation_order;
15use crate::da::Da;
16use crate::elementary::{evaluate_series, exp};
17use crate::error::{codes, dace_panic};
18
19pub(crate) mod netlib_psi_zeta;
20
21fn bessel_jy_orders(x: f64, n0: i32, n1: i32, bessel_y: bool) -> Option<Vec<f64>> {
29 let max_order = n0.unsigned_abs().max(n1.unsigned_abs());
30 let mut b = Vec::with_capacity(max_order as usize + 1);
31 for k in 0..=max_order {
32 b.push(if bessel_y {
35 puruspe::Yn(k, x)
36 } else {
37 puruspe::Jn(k, x)
38 });
39 }
40 let mut out = Vec::with_capacity((n1 - n0 + 1) as usize);
41 let mut s = if n0 % 2 == 0 { 1.0 } else { -1.0 };
42 for i in n0..=n1 {
43 if i >= 0 {
44 out.push(b[i as usize]);
45 } else {
46 out.push(s * b[i.unsigned_abs() as usize]);
47 s *= -1.0;
48 }
49 }
50 Some(out)
51}
52
53fn bessel_ik_orders(x: f64, n0: i32, n1: i32, bessel_k: bool, scaled: bool) -> Option<Vec<f64>> {
62 let max_order = n0.unsigned_abs().max(n1.unsigned_abs());
63 let mut b = Vec::with_capacity(max_order as usize + 1);
64 for k in 0..=max_order {
65 if bessel_k {
66 b.push(if scaled {
67 puruspe::Kn(k, x) * x.exp()
68 } else {
69 puruspe::Kn(k, x)
70 });
71 } else {
72 b.push(if scaled {
73 puruspe::In(k, x) * (-x).exp()
74 } else {
75 puruspe::In(k, x)
76 });
77 }
78 }
79 Some((n0..=n1).map(|i| b[i.unsigned_abs() as usize]).collect())
81}
82
83fn evaluate_bessel_function(a: &Da, bz: &[f64], kind: f64, kkind: f64) -> Da {
91 let nocut = truncation_order();
92 let mut xf = vec![0.0; nocut as usize + 1];
93 let mut binomial = vec![0.0; nocut as usize + 1];
94
95 xf[0] = bz[nocut as usize];
96 binomial[0] = 1.0;
97 let mut factor = 1.0;
98 for i in 1..=nocut as usize {
99 factor *= kkind * 0.5 / i as f64;
100 binomial[i] = 1.0;
102 for j in (1..i).rev() {
103 binomial[j] += binomial[j - 1];
104 }
105 let mut sign = 1.0;
107 let mut c = 0.0;
108 xf[i] = 0.0;
109 for j in 0..=i {
110 let y = binomial[j] * sign * bz[nocut as usize - i + 2 * j] - c;
111 let t = xf[i] + y;
112 c = (t - xf[i]) - y;
113 xf[i] = t;
114 sign *= kind;
115 }
116 xf[i] *= factor;
117 }
118
119 evaluate_series(a, &xf)
120}
121
122fn evaluate_scaled_modified_bessel_function(a: &Da, bz: &[f64], kkind: f64) -> Da {
126 let nocut = truncation_order();
127 let mut xf = vec![0.0; nocut as usize + 1];
128 let mut binomial = vec![0.0; 2 * nocut as usize + 1];
129
130 xf[0] = bz[nocut as usize];
131 binomial[0] = 1.0;
132 let mut factor = 1.0;
133 for i in 1..=nocut as usize {
134 factor *= kkind * 0.5 / i as f64;
135 binomial[2 * i - 1] = 1.0;
137 for j in (1..2 * i - 1).rev() {
138 binomial[j] += binomial[j - 1];
139 }
140 binomial[2 * i] = 1.0;
141 for j in (1..2 * i).rev() {
142 binomial[j] += binomial[j - 1];
143 }
144 let mut sign = 1.0;
145 let mut c = 0.0;
146 xf[i] = 0.0;
147 for j in 0..=2 * i {
148 let y = binomial[j] * sign * bz[nocut as usize - i + j] - c;
149 let t = xf[i] + y;
150 c = (t - xf[i]) - y;
151 xf[i] = t;
152 sign *= -1.0;
153 }
154 xf[i] *= factor;
155 }
156
157 evaluate_series(a, &xf)
158}
159
160pub fn bessel_j(a: &Da, n: i32) -> Da {
166 bessel_common(a, n, false, "bessel_j")
167}
168
169pub fn bessel_y(a: &Da, n: i32) -> Da {
172 bessel_common(a, n, false, "bessel_y")
173}
174
175pub fn bessel_i(a: &Da, n: i32, scaled: bool) -> Da {
178 bessel_common(a, n, scaled, "bessel_i")
179}
180
181pub fn bessel_k(a: &Da, n: i32, scaled: bool) -> Da {
189 bessel_common(a, n, scaled, "bessel_k")
190}
191
192fn bessel_common(a: &Da, n: i32, scaled: bool, kind: &str) -> Da {
193 let a0 = a.cons();
194 if a0 <= 0.0 {
195 dace_panic(codes::OUT_OF_DOMAIN, "Out of domain");
196 }
197 let nocut = truncation_order() as i32;
198 let n0 = n - nocut;
199 let n1 = n + nocut;
200 let bz = match kind {
201 "bessel_i" => bessel_ik_orders(a0, n0, n1, false, scaled),
202 "bessel_k" => bessel_ik_orders(a0, n0, n1, true, scaled),
203 "bessel_j" => bessel_jy_orders(a0, n0, n1, false),
204 _ => bessel_jy_orders(a0, n0, n1, true),
205 };
206 match bz {
207 Some(bz) => match kind {
208 "bessel_j" | "bessel_y" => evaluate_bessel_function(a, &bz, -1.0, 1.0),
209 "bessel_k" if scaled => evaluate_scaled_modified_bessel_function(a, &bz, -1.0),
210 "bessel_k" => evaluate_bessel_function(a, &bz, 1.0, -1.0),
211 _ if scaled => evaluate_scaled_modified_bessel_function(a, &bz, 1.0),
212 _ => evaluate_bessel_function(a, &bz, 1.0, 1.0),
213 },
214 None => dace_panic(codes::OUT_OF_DOMAIN, "Out of domain"),
215 }
216}
217
218fn log_gamma0(a: &Da, a0: f64) -> Da {
225 let nocut = truncation_order();
226 let mut xf = vec![0.0; nocut as usize + 1];
227 xf[0] = 0.0;
228 xf[1] = netlib_psi_zeta::psi(a0);
229 let mut s = 1.0;
230 for (i, item) in xf.iter_mut().enumerate().skip(2) {
231 *item = (s / i as f64) * netlib_psi_zeta::zeta(i as f64, a0);
232 s *= -1.0;
233 }
234 evaluate_series(a, &xf)
235}
236
237pub fn log_gamma(a: &Da) -> Da {
244 let a0 = a.cons();
245 if a0 <= 0.0 && a0.trunc() == a0 {
246 dace_panic(codes::OUT_OF_DOMAIN, "Out of domain");
247 }
248 let mut r = log_gamma0(a, a0);
249 let c = puruspe::gamma(a0).ln();
250 r.set_coefficient0(0, c);
251 r
252}
253
254pub fn gamma(a: &Da) -> Da {
261 let a0 = a.cons();
262 if a0 <= 0.0 && a0.trunc() == a0 {
263 dace_panic(codes::OUT_OF_DOMAIN, "Out of domain");
264 }
265 let lg = log_gamma0(a, a0);
266 let mut r = exp(&lg);
267 let g = puruspe::gamma(a0);
269 r = crate::kernels::weighted_sum(&r, g, &r, 0.0);
270 r
271}
272
273pub fn psi(a: &Da, n: u32) -> Da {
281 let a0 = a.cons();
282 if a0 <= 0.0 && a0.trunc() == a0 {
283 dace_panic(codes::OUT_OF_DOMAIN, "Out of domain");
284 }
285
286 let nocut = truncation_order();
287 let mut xf = vec![0.0; nocut as usize + 1];
288
289 if n == 0 {
290 xf[0] = netlib_psi_zeta::psi(a0);
291 let mut s = 1.0;
292 for (i, item) in xf.iter_mut().enumerate().skip(1) {
293 *item = s * netlib_psi_zeta::zeta(f64::from(i as u32 + 1), a0);
294 s *= -1.0;
295 }
296 } else {
297 let mut fac = if n % 2 != 0 { 1.0 } else { -1.0 };
299 for i in 2..=n {
300 fac *= i as f64;
301 }
302 for (i, item) in xf.iter_mut().enumerate() {
303 *item = fac * netlib_psi_zeta::zeta(f64::from(n + i as u32 + 1), a0);
304 fac = -(fac / (i as f64 + 1.0)) * f64::from(n + i as u32 + 1);
305 }
306 }
307
308 evaluate_series(a, &xf)
309}
310
311impl Da {
312 pub fn bessel_j(&self, n: i32) -> Da {
314 bessel_j(self, n)
315 }
316
317 pub fn bessel_y(&self, n: i32) -> Da {
319 bessel_y(self, n)
320 }
321
322 pub fn bessel_i(&self, n: i32, scaled: bool) -> Da {
324 bessel_i(self, n, scaled)
325 }
326
327 pub fn bessel_k(&self, n: i32, scaled: bool) -> Da {
329 bessel_k(self, n, scaled)
330 }
331
332 pub fn log_gamma(&self) -> Da {
334 log_gamma(self)
335 }
336
337 pub fn gamma(&self) -> Da {
339 gamma(self)
340 }
341
342 pub fn psi(&self, n: u32) -> Da {
344 psi(self, n)
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::test_support::CONTEXT_LOCK;
352
353 #[test]
354 fn netlib_psi_zeta_values() {
355 assert!((netlib_psi_zeta::psi(1.0) + 0.5772156649015329).abs() < 1e-13);
357 assert!(
359 (netlib_psi_zeta::zeta(2.0, 1.0) - std::f64::consts::PI.powi(2) / 6.0).abs() < 1e-12
360 );
361 assert!((netlib_psi_zeta::psi(0.5) + 0.5772156649015329 + 2.0 * 2.0f64.ln()).abs() < 1e-12);
363 assert!(
365 (netlib_psi_zeta::zeta(4.0, 1.0) - std::f64::consts::PI.powi(4) / 90.0).abs() < 1e-10
366 );
367 }
368
369 #[test]
370 fn gamma_and_bessel_constants() {
371 let _g = CONTEXT_LOCK.lock();
372 crate::context::init(6, 2).unwrap();
373
374 let g = gamma(&(0.5 + Da::variable(1)));
376 assert!((g.cons() - std::f64::consts::PI.sqrt()).abs() < 1e-13);
377
378 let lg = log_gamma(&(0.5 + Da::variable(1)));
380 assert!((lg.cons() - puruspe::gamma(0.5).ln()).abs() < 1e-14);
381
382 let j = bessel_j(&(1.0 + Da::variable(1)), 0);
384 assert!((j.cons() - puruspe::besseljy(0.0, 1.0).0).abs() < 1e-13);
385
386 let lin = j.get_coefficient(&[1, 0]);
388 assert!((lin + puruspe::besseljy(1.0, 1.0).0).abs() < 1e-12);
389
390 let p0 = psi(&(1.0 + Da::variable(1)), 0);
392 assert!((p0.cons() - netlib_psi_zeta::psi(1.0)).abs() < 1e-14);
393 assert!((p0.get_coefficient(&[1, 0]) - netlib_psi_zeta::zeta(2.0, 1.0)).abs() < 1e-12);
394
395 let p1 = psi(&(1.0 + Da::variable(1)), 1);
397 assert!((p1.cons() - netlib_psi_zeta::zeta(2.0, 1.0)).abs() < 1e-12);
398 assert!(
399 (p1.get_coefficient(&[1, 0]) + 2.0 * netlib_psi_zeta::zeta(3.0, 1.0)).abs() < 1e-11
400 );
401
402 let kx = 2.0 + Da::variable(1);
404 let ku = bessel_k(&kx, 1, false);
405 let ks = bessel_k(&kx, 1, true);
406 assert!((ks.cons() - ku.cons() * 2.0f64.exp()).abs() < 1e-6 * ks.cons().abs());
407
408 let iu = bessel_i(&kx, 1, false);
410 let is_ = bessel_i(&kx, 1, true);
411 assert!((is_.cons() - iu.cons() * (-2.0f64).exp()).abs() < 1e-13);
412 }
413
414 #[test]
415 fn special_domain_panics() {
416 let _g = CONTEXT_LOCK.lock();
417 crate::context::init(6, 2).unwrap();
418
419 fn expect_code(f: impl FnOnce() -> Da + std::panic::UnwindSafe, code: u32) {
420 let err = std::panic::catch_unwind(f).expect_err("must panic");
421 let e = err
422 .downcast_ref::<crate::error::DaceError>()
423 .expect("DaceError payload");
424 assert_eq!(e.code, code, "{}", e);
425 }
426
427 expect_code(|| gamma(&Da::constant(0.0)), codes::OUT_OF_DOMAIN);
428 expect_code(|| gamma(&Da::constant(-3.0)), codes::OUT_OF_DOMAIN);
429 expect_code(|| log_gamma(&Da::constant(-2.0)), codes::OUT_OF_DOMAIN);
430 expect_code(|| psi(&Da::constant(-1.0), 2), codes::OUT_OF_DOMAIN);
431 expect_code(|| bessel_j(&Da::constant(0.0), 1), codes::OUT_OF_DOMAIN);
432 expect_code(|| bessel_y(&Da::constant(-1.0), 1), codes::OUT_OF_DOMAIN);
433 expect_code(
434 || bessel_i(&Da::constant(-1.0), 1, false),
435 codes::OUT_OF_DOMAIN,
436 );
437 expect_code(
438 || bessel_k(&Da::constant(0.0), 1, true),
439 codes::OUT_OF_DOMAIN,
440 );
441 }
442}