1use std::f64::consts::{FRAC_1_SQRT_2, PI};
15
16use errorfunctions::ComplexErrorFunctions;
17
18use super::{
19 double::DoubleComplex,
20 gamma::{RECURRENCE_GROWTH, ScaledGammaLadder},
21};
22use crate::{Complex, numerics::finite};
23
24pub(crate) fn kambe_argument(z: Complex, eta: Complex) -> Complex {
28 0.5 * (z * z) * eta * eta
29}
30
31const SERIES_PEAK_LIMIT: f64 = 1000.0;
35
36fn kambe_series<const K: usize>(orders: [i32; K], z: Complex, eta: Complex) -> [(Complex, f64); K] {
43 kambe_series_with(orders, z, eta, None)
44}
45
46fn series_terms(ratio: Complex) -> f64 {
49 200.0 + 3.0 * ratio.norm().min(SERIES_PEAK_LIMIT)
50}
51
52fn kambe_series_with<const K: usize>(
54 orders: [i32; K],
55 z: Complex,
56 eta: Complex,
57 table: Option<&[Complex]>,
58) -> [(Complex, f64); K] {
59 struct Partial {
60 start: i32,
61 coefficient: Complex,
62 sum: Complex,
63 magnitude: f64,
64 converged: bool,
65 }
66 let top = orders.into_iter().max().unwrap_or_default();
67 let even = top % 2 == 0;
68 let z = if even && z.re < 0.0 { -z } else { z };
69 let square = z * z;
70 let argument = kambe_argument(z, eta);
71 let reflected =
72 even && (crate::numerics::complex_sqrt(square * eta * eta) * (z * eta).conj()).re < 0.0;
73 let ratio = 0.5 / (eta * eta);
74 let peak = ratio.norm();
75 let last = series_terms(ratio);
76 let mut ladder = ScaledGammaLadder::new(0.5 * f64::from(top + 1), argument, reflected);
77 let mut partials = orders.map(|n| Partial {
79 start: (top - n) / 2,
80 coefficient: Complex::new(1.0, 0.0),
81 sum: Complex::default(),
82 magnitude: 0.0,
83 converged: false,
84 });
85 let mut step = 0;
86 loop {
87 let value = ladder.next_lower();
88 for partial in partials
89 .iter_mut()
90 .filter(|partial| partial.start <= step && !partial.converged)
91 {
92 let index = step - partial.start;
93 let j = f64::from(index);
94 if let Some(table) = table {
95 partial.coefficient = usize::try_from(index)
96 .ok()
97 .and_then(|index| table.get(index))
98 .copied()
99 .unwrap_or_default();
100 } else if j > 0.0 {
101 partial.coefficient *= ratio / j;
102 }
103 let term = partial.coefficient * value;
104 let modulus = term.l1_norm();
105 partial.sum += term;
106 partial.magnitude += modulus;
107 let small = if table.is_some() {
110 modulus <= 1.4e-16 * partial.sum.l1_norm()
111 } else {
112 term.norm() <= 2e-16 * partial.sum.norm()
113 };
114 partial.converged = (j > 3.0 && j > peak && small) || j >= last || !finite(partial.sum);
115 }
116 if partials.iter().all(|partial| partial.converged) {
117 break;
118 }
119 step += 1;
120 }
121 std::array::from_fn(|i| {
122 let prefactor = 0.5 * eta.powi(orders[i] + 1);
123 (
124 prefactor * partials[i].sum,
125 prefactor.norm() * partials[i].magnitude,
126 )
127 })
128}
129
130fn kambe_below_base(n: i32, z: Complex, eta: Complex) -> (Complex, f64) {
138 let ratio = 0.5 / (eta * eta);
139 let odd = n % 2 != 0;
140 let cancellation = ratio.norm() - ratio.re;
141 if cancellation > RECURRENCE_GROWTH.ln() {
142 if odd {
143 let [series, lower, upper] = kambe_series([n, -3, -1], z, eta);
144 return more_accurate(series, kambe_downward(n, z, eta, [lower, upper]));
145 }
146 let recurred = kambe_downward(n, z, eta, even_pair(z, eta));
147 let growth = recurred.1 / recurred.0.norm();
148 if growth <= RECURRENCE_GROWTH || cancellation.exp() >= RECURRENCE_GROWTH * growth {
149 return recurred;
150 }
151 let [series] = kambe_series([n], z, eta);
152 return more_accurate(series, recurred);
153 }
154 let [series] = kambe_series([n], z, eta);
155 if series.1 <= RECURRENCE_GROWTH * series.0.norm() {
156 return series;
157 }
158 more_accurate(series, kambe_downward(n, z, eta, base_pair(odd, z, eta)))
159}
160
161fn more_accurate(series: (Complex, f64), recurred: (Complex, f64)) -> (Complex, f64) {
163 if recurred.1 < series.1 || !finite(series.0) {
164 recurred
165 } else {
166 series
167 }
168}
169
170fn kambe_downward(n: i32, z: Complex, eta: Complex, pair: [(Complex, f64); 2]) -> (Complex, f64) {
174 let [(mut lower, mut lower_error), (mut upper, mut upper_error)] = pair;
175 if z == Complex::default() {
177 (upper, upper_error) = (Complex::default(), 0.0);
178 }
179 let square = z * z;
180 let exponential = (0.5 * (1.0 / (eta * eta) - square * eta * eta)).exp();
181 let mut order = if n % 2 == 0 { -4 } else { -5 };
182 while order >= n {
183 let endpoint = eta.powi(order + 3) * exponential;
184 let value = f64::from(order + 3) * lower - square * upper + endpoint;
185 let error = f64::from(order + 3).abs() * lower_error
186 + square.norm() * upper_error
187 + endpoint.l1_norm();
188 (upper, upper_error) = (lower, lower_error);
189 (lower, lower_error) = (value, error);
190 order -= 2;
191 }
192 (lower, lower_error)
193}
194
195fn base_pair(odd: bool, z: Complex, eta: Complex) -> [(Complex, f64); 2] {
198 if odd {
199 kambe_series([-3, -1], z, eta)
200 } else {
201 even_pair(z, eta)
202 }
203}
204
205fn even_pair(z: Complex, eta: Complex) -> [(Complex, f64); 2] {
208 let (terms, z) = even_terms(z, eta);
209 let [lower, upper] = even_values(terms, z);
210 let magnitude = even_scale() * (terms[0].norm() + terms[1].norm());
211 [(lower, magnitude), (upper, magnitude / z.norm())]
212}
213
214fn even_scale() -> f64 {
216 0.5 * PI.sqrt() * FRAC_1_SQRT_2
217}
218
219fn even_terms(z: Complex, eta: Complex) -> ([Complex; 2], Complex) {
222 let z = if z.re < 0.0 { -z } else { z };
223 let plus = ((z * eta - Complex::i() / eta) * FRAC_1_SQRT_2).erfc() * (-Complex::i() * z).exp();
224 let minus = ((z * eta + Complex::i() / eta) * FRAC_1_SQRT_2).erfc() * (Complex::i() * z).exp();
225 ([plus, minus], z)
226}
227
228fn even_values([plus, minus]: [Complex; 2], z: Complex) -> [Complex; 2] {
230 let scale = even_scale();
231 [
232 -Complex::i() * scale * (plus - minus),
233 scale * (plus + minus) / z,
234 ]
235}
236
237#[derive(Clone, Debug)]
242pub(crate) struct EvenKambe {
243 argument: Complex,
245 square: Complex,
246 eta: Complex,
247 exponential: Complex,
248 terms: [f64; 2],
250 values: Vec<Complex>,
252}
253
254impl EvenKambe {
255 pub(crate) fn new(z: Complex, eta: Complex) -> Self {
257 let (terms, argument) = even_terms(z, eta);
258 let [lower, upper] = even_values(terms, argument);
259 let square = z * z;
260 Self {
261 argument,
262 square,
263 eta,
264 exponential: (0.5 * (1.0 / (eta * eta) - square * eta * eta)).exp(),
265 terms: terms.map(Complex::norm),
266 values: vec![upper, lower],
267 }
268 }
269
270 pub(crate) fn single(&self, n: i32) -> (Complex, f64) {
274 kambe_with_bound(n, self.argument, self.eta)
275 }
276
277 pub(crate) fn sum_bounds(&self, weights: &[Complex]) -> (f64, f64) {
283 let values = &self.values;
284 let moduli = weights
285 .iter()
286 .zip(values)
287 .map(|(weight, value)| weight.l1_norm() * value.l1_norm())
288 .sum();
289 let (mut next, mut after) = (Complex::default(), Complex::default());
290 let mut bound = 0.0;
291 for (j, &weight) in weights.iter().enumerate().rev() {
292 let order = i32::try_from(j).unwrap_or(i32::MAX / 2);
295 let step = if j >= 1 {
296 f64::from(1 - 2 * order)
297 } else {
298 0.0
299 };
300 let adjoint = weight + step * next - self.square * after;
301 if let (Some(&lower), Some(&upper)) =
302 (values.get(j.wrapping_sub(1)), values.get(j.wrapping_sub(2)))
303 && j >= 2
304 {
305 let endpoint = self.eta.powi(3 - 2 * order) * self.exponential;
306 let rounding = (f64::from(3 - 2 * order) * lower).l1_norm()
307 + (self.square * upper).l1_norm()
308 + endpoint.l1_norm();
309 bound += adjoint.l1_norm() * rounding;
310 }
311 (after, next) = (next, adjoint);
312 }
313 let (zero, lower) = (next, after);
315 let scale = even_scale();
316 let sum = zero / self.argument;
317 let difference = Complex::i() * lower;
318 let pair = scale
319 * (self.terms[0] * (sum - difference).l1_norm()
320 + self.terms[1] * (sum + difference).l1_norm());
321 (bound + pair, moduli)
322 }
323
324 pub(crate) fn get(&mut self, n: i32) -> Complex {
326 debug_assert!(n <= 2 && n % 2 == 0, "even Kambe order {n} above 2");
327 if n > 0 {
328 return (self.values[0] - self.values[1] + self.eta * self.exponential) / self.square;
330 }
331 let index = usize::try_from(-n / 2).unwrap_or_default();
332 let mut order = -2 * i32::try_from(self.values.len()).unwrap_or(i32::MAX / 2);
333 while order >= n {
334 let length = self.values.len();
335 let (upper, lower) = (self.values[length - 2], self.values[length - 1]);
336 self.values.push(
337 f64::from(order + 3) * lower - self.square * upper
338 + self.eta.powi(order + 3) * self.exponential,
339 );
340 order -= 2;
341 }
342 self.values[index]
343 }
344}
345
346pub(crate) fn kambe(n: i32, z: Complex, eta: Complex) -> Complex {
353 let zero = Complex::default();
354 if eta == zero || (z == zero && n > -2) {
355 return Complex::new(f64::INFINITY, 0.0);
356 }
357 let odd = n % 2 != 0;
358 let base = if odd { -3 } else { -2 };
359 if n < base || (odd && n <= -1) {
361 return kambe_with_bound(n, z, eta).0;
362 }
363 let pair = if odd {
365 kambe_series([-3, -1], z, eta).map(|(value, _)| (value, 0.0))
366 } else {
367 let (terms, argument) = even_terms(z, eta);
368 let values = even_values(terms, argument);
369 if n == base {
371 return values[0];
372 }
373 values.map(|value| (value, 0.0))
374 };
375 let [(lower, _), (upper, _)] = pair;
376 if n <= base + 2 {
377 return if n == base { lower } else { upper };
378 }
379 kambe_upward::<false>(n, z, eta, pair, 1.0).0
380}
381
382pub(crate) fn kambe_with_bound(n: i32, z: Complex, eta: Complex) -> (Complex, f64) {
388 let odd = n % 2 != 0;
389 let base = if odd { -3 } else { -2 };
390 if n < base {
391 return kambe_below_base(n, z, eta);
392 }
393 if odd && n <= -1 {
394 let [series] = kambe_series([n], z, eta);
395 return series;
396 }
397 let [lower, upper] = base_pair(odd, z, eta);
398 if n <= base + 2 {
399 return if n == base { lower } else { upper };
400 }
401 kambe_upward::<true>(n, z, eta, [lower, upper], 1.0)
402}
403
404#[derive(Clone, Debug)]
413pub(crate) struct SmallSplitKambe {
414 eta: Complex,
415 cancellation: f64,
417 coefficients: Vec<Complex>,
418}
419
420impl SmallSplitKambe {
421 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] pub(crate) fn new(eta: Complex) -> Self {
424 let ratio = 0.5 / (eta * eta);
425 let terms = series_terms(ratio) as usize + 1;
426 let c = DoubleComplex::half_inverse_square(eta);
427 let mut coefficient = DoubleComplex::from(Complex::new(1.0, 0.0));
428 let coefficients = (0..terms)
429 .map(|j| {
430 if j > 0 {
431 #[allow(clippy::cast_precision_loss)] let j = j as f64;
433 coefficient = (coefficient * c).divide(j);
434 }
435 coefficient.rounded()
436 })
437 .collect();
438 Self {
439 eta,
440 cancellation: ratio.norm() - ratio.re,
441 coefficients,
442 }
443 }
444
445 pub(crate) fn get<const K: usize>(
452 &self,
453 orders: [i32; K],
454 z: Complex,
455 floor: f64,
456 ) -> [(Complex, f64); K] {
457 let eta = self.eta;
458 let ulps = SERIES_ULPS + kambe_argument(z, eta).norm();
461 let closed = orders.map(|n| self.closed(n, z, ulps));
462 let modulus = closed[0].0.norm();
463 if finite(closed[0].0)
465 && (closed[0].1 <= floor.max(CLOSED_ULPS * modulus)
466 || self.cancellation.exp() * ulps * modulus >= closed[0].1)
467 {
468 return closed;
469 }
470 let series = kambe_series_with(orders, z, eta, Some(&self.coefficients))
471 .map(|(value, bound)| (value, ulps * bound));
472 if finite(series[0].0) && (series[0].1 <= closed[0].1 || !finite(closed[0].0)) {
473 series
474 } else {
475 closed
476 }
477 }
478
479 fn closed(&self, n: i32, z: Complex, ulps: f64) -> (Complex, f64) {
483 let eta = self.eta;
484 let odd = n % 2 != 0;
485 let pair = if odd {
486 kambe_series_with([-3, -1], z, eta, Some(&self.coefficients))
487 .map(|(value, bound)| (value, ulps * bound))
488 } else {
489 let (terms, argument) = even_terms(z, eta);
490 let [lower, upper] = even_values(terms, argument);
491 let ulps =
493 |sign: f64| 1.0 + 0.5 * (argument * eta + sign * Complex::i() / eta).norm_sqr();
494 let magnitude = even_scale()
495 * terms[0]
496 .norm()
497 .mul_add(ulps(-1.0), terms[1].norm() * ulps(1.0));
498 [(lower, magnitude), (upper, magnitude / argument.norm())]
499 };
500 let base = if odd { -3 } else { -2 };
501 debug_assert!(n >= base, "Kambe order {n} below the base pair");
502 if n <= base + 2 {
503 return pair[usize::from(n != base)];
504 }
505 let exponent = 0.5 * (1.0 / (eta * eta) - z * z * eta * eta);
506 kambe_upward::<true>(n, z, eta, pair, 1.0 + exponent.norm())
507 }
508}
509
510const CLOSED_ULPS: f64 = 64.0;
513
514const SERIES_ULPS: f64 = 32.0;
518
519fn kambe_upward<const BOUND: bool>(
523 n: i32,
524 z: Complex,
525 eta: Complex,
526 pair: [(Complex, f64); 2],
527 endpoint_ulps: f64,
528) -> (Complex, f64) {
529 let [(mut lower, mut lower_error), (mut upper, mut upper_error)] = pair;
530 let exponential = (0.5 * (1.0 / (eta * eta) - z * z * eta * eta)).exp();
531 let square = if BOUND { (z * z).norm() } else { 0.0 };
532 let mut order = if n % 2 == 0 { 2 } else { 1 };
533 while order <= n {
534 let endpoint = eta.powi(order - 1) * exponential;
535 let value = (f64::from(order - 1) * upper - lower + endpoint) / (z * z);
536 let error = if BOUND {
537 (f64::from(order - 1).abs() * upper_error
538 + lower_error
539 + endpoint_ulps * endpoint.l1_norm())
540 / square
541 + value.l1_norm()
542 } else {
543 0.0
544 };
545 (lower, lower_error) = (upper, upper_error);
546 (upper, upper_error) = (value, error);
547 order += 2;
548 }
549 (upper, upper_error)
550}
551
552#[cfg(test)]
553mod tests {
554 use super::{
558 SmallSplitKambe, base_pair, even_pair, kambe, kambe_downward, kambe_series,
559 kambe_with_bound,
560 };
561 use crate::{
562 Complex,
563 numerics::finite,
564 special::integrals::{intkambe, intkambe_array, reference},
565 test_support::{ALGEBRA_CASES, central, complex, prop_assert_close},
566 };
567 use proptest::{prelude::*, test_runner::TestCaseError};
568
569 #[test]
572 fn intkambe_matches_quadrature() {
573 let cases = reference(include_str!(concat!(
574 env!("CARGO_MANIFEST_DIR"),
575 "/references/kambe.txt"
576 )));
577 assert_eq!(cases.len(), 181);
578 for (key, expected) in cases {
579 #[allow(clippy::cast_possible_truncation)] let n = key[0] as i32;
581 let (z, eta) = (Complex::new(key[1], key[2]), Complex::new(key[3], 0.0));
582 let actual = intkambe(n, z, eta).unwrap();
583 assert!(
584 (actual - expected).norm() <= 1e-13 * expected.norm(),
585 "I_{n}({z}, {eta}) = {actual}, expected {expected}"
586 );
587 }
588 }
589
590 const SUM_ROUNDING: f64 = 256.0;
595
596 #[test]
604 fn intkambe_at_lattice_arguments() {
605 let cases = reference(include_str!(concat!(
606 env!("CARGO_MANIFEST_DIR"),
607 "/references/kambe_lattice.txt"
608 )));
609 assert_eq!(cases.len(), 1249);
610 for (key, expected) in cases {
611 #[allow(clippy::cast_possible_truncation)] let n = key[0] as i32;
613 let (z, eta) = (Complex::new(key[1], key[2]), Complex::new(key[3], key[4]));
614 let actual = intkambe(n, z, eta).unwrap();
615 let odd = n % 2 != 0;
616 let [series] = kambe_series([n], z, eta);
617 let bound = if n < -3 {
618 series
619 .1
620 .min(kambe_downward(n, z, eta, base_pair(odd, z, eta)).1)
621 } else if odd {
622 series.1
623 } else {
624 even_pair(z, eta)[usize::from(n == 0)].1
625 };
626 let error = (actual - expected).norm();
627 assert!(
628 error <= 1e-13 * expected.norm() + SUM_ROUNDING * f64::EPSILON * bound,
629 "I_{n}({z}, {eta}) = {actual}, expected {expected}, bound {bound}"
630 );
631 assert!(
632 n % 2 != 0 || error <= 1e-11 * expected.norm(),
633 "I_{n}({z}, {eta}) = {actual}, expected {expected}"
634 );
635 let (value, reported) = kambe_with_bound(n, z, eta);
637 assert_eq!(value, actual, "I_{n}({z}, {eta})");
638 assert!(
639 error <= 1e-13 * expected.norm() + SUM_ROUNDING * f64::EPSILON * reported,
640 "I_{n}({z}, {eta}) = {actual}, expected {expected}, bound {reported}"
641 );
642 }
643 }
644
645 #[test]
650 fn kambe_series_at_small_arguments() {
651 for (key, expected) in reference(SMALL_ARGUMENTS) {
652 #[allow(clippy::cast_possible_truncation)] let n = key[0] as i32;
654 let (z, eta) = (Complex::new(key[1], key[2]), Complex::new(key[3], key[4]));
655 let actual = intkambe(n, z, eta).unwrap();
656 assert!(
657 (actual - expected).norm() <= 1e-13 * expected.norm(),
658 "I_{n}({z}, {eta}) = {actual}, expected {expected}"
659 );
660 }
661 let eta = Complex::new(0.9, 0.2);
662 let expected = (0.5 / (eta * eta)).exp() - 1.0;
663 let actual = intkambe(-3, Complex::default(), eta).unwrap();
664 assert!((actual - expected).norm() <= 1e-15 * expected.norm());
665 }
666
667 const SMALL_ARGUMENTS: &str = "
669 -11 1e-8 0.0 1.0 0.0: 0.1520560731298582 0.0
670 -4 1e-12 1e-12 0.0 -1.4285714285714286: 6.468012052193277e-25 -0.09890802845085557
671 -60 1e-6 0.0 0.8 0.0: 18842.496102322668 0.0";
672
673 #[test]
679 fn kambe_series_at_far_evanescent_orders() {
680 for (key, expected) in reference(FAR_EVANESCENT_ORDERS) {
681 #[allow(clippy::cast_possible_truncation)] let n = key[0] as i32;
683 let (x, eta) = (Complex::new(key[1], key[2]), Complex::new(key[3], key[4]));
684 let actual = intkambe(n, x, eta).unwrap();
685 let (value, bound) = kambe_with_bound(n, x, eta);
686 assert_eq!(value, actual, "I_{n}({x}, {eta})");
687 let error = (actual - expected).norm();
688 let tolerance = 1e-13 * expected.norm() + SUM_ROUNDING * f64::EPSILON * bound;
689 assert!(
690 finite(actual) && error <= tolerance,
691 "I_{n}({x}, {eta}) = {actual}, expected {expected}, bound {bound}"
692 );
693 if eta.im == -0.2 {
694 assert!(error <= 1e-4 * expected.norm(), "I_{n}({x}, {eta})");
695 } else {
696 assert!(
698 f64::EPSILON * bound >= expected.norm(),
699 "I_{n}({x}, {eta}): bound {bound}"
700 );
701 }
702 }
703 }
704
705 const FAR_EVANESCENT_ORDERS: &str = "
708 1 0.0 65.0 0.0 -0.2: -2.0660582937881563e-46 0.0
709 -3 0.0 65.0 0.0 -0.2: -1.2571235926483948e-43 0.0
710 -1 0.0 72.75 0.0 -0.2: 2.1076570255627118e-54 0.0
711 -5 0.0 72.75 0.0 -0.2: 1.2903462855893798e-51 0.0
712 -3 0.0 74.0 0.0 -0.2: -1.2811250838191011e-54 0.0
713 -9 0.0 74.0 0.0 -0.2: 1.9436884363619354e-50 0.0
714 -5 0.0 100.0 0.0 -0.2: 8.455104242709188e-93 0.0
715 1 0.0 100.0 0.0 -0.2: -5.497277311462082e-97 0.0
716 -9 0.0 104.0 0.0 -0.125: 3.6443751390988474e-46 0.0
717 -1 0.0 104.0 0.0 -0.125: 2.3262288907012024e-53 0.0
718 1 0.0 116.4 0.0 -0.125: -1.4178141658789873e-64 0.0
719 -3 0.0 116.4 0.0 -0.125: -5.65917325634384e-61 0.0
720 -1 0.0 118.4 0.0 -0.125: 2.1799896234970922e-64 0.0
721 -5 0.0 118.4 0.0 -0.125: 8.713921867089539e-61 0.0
722 -3 0.0 160.0 0.0 -0.125: -3.292074055899649e-102 0.0
723 -9 0.0 160.0 0.0 -0.125: 8.481758744830575e-97 0.0
724 -5 0.0 156.0 0.0 -0.08333333333333333: 5.401514253857145e-66 0.0
725 1 0.0 156.0 0.0 -0.08333333333333333: -2.0476729683120954e-72 0.0
726 -9 0.0 174.60000000000002 0.0 -0.08333333333333333: 2.9610339101407296e-71 0.0
727 -1 0.0 174.60000000000002 0.0 -0.08333333333333333: 7.549716659238215e-80 0.0
728 1 0.0 177.60000000000002 0.0 -0.08333333333333333: -1.254520496883189e-83 0.0
729 -3 0.0 177.60000000000002 0.0 -0.08333333333333333: -2.4885065995274332e-79 0.0
730 -1 0.0 240.0 0.0 -0.08333333333333333: 2.8622298120865214e-121 0.0
731 -5 0.0 240.0 0.0 -0.08333333333333333: 5.8465689416040285e-117 0.0";
732
733 #[test]
738 fn small_split_integrals_keep_their_digits() {
739 for (key, expected) in reference(SMALL_SPLITS) {
740 #[allow(clippy::cast_possible_truncation)] let n = key[0] as i32;
742 let (z, eta) = (Complex::new(key[1], key[2]), Complex::new(key[3], key[4]));
743 let [(value, bound)] = SmallSplitKambe::new(eta).get([n], z, 0.0);
744 let error = (value - expected).norm();
745 assert!(
746 error <= 32.0 * f64::EPSILON * expected.norm() && error <= f64::EPSILON * bound,
747 "I_{n}({z}, {eta}) = {value}, expected {expected}, bound {bound}"
748 );
749 }
750 }
751
752 const SMALL_SPLITS: &str = "
754 10 0.51 0.0 0.135 0.0: 1979290.8095660476 0.0
755 14 1.19 0.0 0.135 0.0: 13164.26182468963 0.0
756 0 0.21 0.0 0.137 0.0: 1013302395.488461 0.0
757 2 0.17 0.0 0.135 0.0: 40717456.33548243 0.0
758 13 0.65 0.2 0.17 0.0: -5355697.287078753 8848712.085330464
759 8 1.59 0.13 0.166 -0.0513: 1.8142292945819555 -1.5315951220636088";
760
761 #[test]
766 fn kambe_series_at_small_eta() {
767 let eta = Complex::new(0.0266, 0.0);
768 let expected = (0.5 / (eta * eta)).exp() - 1.0;
769 let actual = intkambe(-3, Complex::default(), eta).unwrap();
770 assert!((actual - expected).norm() <= 1e-14 * expected.norm());
771 let cases = [
772 (-1, 1.0, 1e-5),
773 (-4, 1.0, 1e-8),
774 (-3, 0.0, 1e-6),
775 (3, 1.0, 1e-6),
776 ];
777 let (sender, receiver) = std::sync::mpsc::channel();
778 std::thread::spawn(move || {
779 let values = cases.map(|(n, z, eta)| {
780 intkambe(n, Complex::new(z, 0.0), Complex::new(eta, 0.0)).unwrap()
781 });
782 sender.send(values).unwrap();
783 });
784 let values = receiver
785 .recv_timeout(std::time::Duration::from_secs(10))
786 .expect("the Kambe series must end within a bounded number of terms");
787 for ((n, z, eta), value) in cases.into_iter().zip(values) {
788 assert!(!finite(value), "I_{n}({z}, {eta}) = {value}");
789 }
790 }
791
792 proptest! {
793 #![proptest_config(ProptestConfig::with_cases(ALGEBRA_CASES))]
794
795 #[test]
796 fn kambe_series_and_recurrence(
797 n in -16_i32..=4,
798 z in complex(3.0),
799 (radius, phase) in (0.4_f64..3.0, -3.0_f64..1.5),
800 ) {
801 check_kambe_series(n, z, Complex::from_polar(radius, phase))?;
802 }
803
804 #[test]
805 fn kambe_argument_derivatives_and_reflection(
806 n in -8_i32..8, x in 0.5_f64..2.0, eta in 0.8_f64..1.8,
807 ) {
808 check_kambe(n, Complex::new(x, 0.1), Complex::new(eta, 0.03))?;
809 }
810 }
811
812 fn check_kambe_series(n: i32, z: Complex, eta: Complex) -> Result<(), TestCaseError> {
818 prop_assume!(z.norm() > 1e-3);
819 let pairs = kambe_series([-2, 0], z, eta)
824 .into_iter()
825 .zip(even_pair(z, eta));
826 for (order, ((series, magnitude), (expected, _))) in [-2, 0].into_iter().zip(pairs) {
827 prop_assert_close!(
828 series,
829 expected,
830 1e-12 * expected.norm() + SUM_ROUNDING * f64::EPSILON * magnitude,
831 "order {}",
832 order
833 );
834 }
835 if n % 2 != 0 && n < -3 {
836 let orders = [n, -3, -1];
837 let single = orders.map(|order| kambe_series([order], z, eta)[0].0);
838 for ((series, magnitude), expected) in
839 kambe_series(orders, z, eta).into_iter().zip(single)
840 {
841 prop_assert_close!(
842 series,
843 expected,
844 1e-13 * expected.norm() + SUM_ROUNDING * f64::EPSILON * magnitude
845 );
846 }
847 }
848 let [value, middle, upper] = [n, n + 2, n + 4].map(|order| kambe(order, z, eta));
849 let endpoint = eta.powi(n + 3) * (0.5 * (1.0 / (eta * eta) - z * z * eta * eta)).exp();
850 let terms = [value, f64::from(n + 3) * middle, z * z * upper, endpoint];
851 let scale = terms.iter().map(|t| t.norm()).fold(0.0, f64::max);
852 let tolerance = if n % 2 == 0 && n + 4 >= -2 {
854 1e-12
855 } else {
856 1e-13
857 };
858 prop_assert_close!(value - terms[1] + terms[2], endpoint, tolerance * scale);
859 Ok(())
860 }
861
862 fn check_kambe(n: i32, z: Complex, eta: Complex) -> Result<(), TestCaseError> {
865 let g = Complex::new(0.7, -0.2);
866 let (_, residual) = intkambe_array(vec![n], [vec![z], vec![eta]]).unwrap();
867 let gradient = residual.pullback(&[g]).unwrap();
868 let delta = Complex::new(0.3, 0.4);
869 let differences = [
870 central(1e-5, |h| intkambe(n, z + h * delta, eta).unwrap()),
871 central(1e-5, |h| intkambe(n, z, eta + h * delta).unwrap()),
872 ];
873 for (gradient, difference) in gradient.iter().zip(differences) {
874 prop_assert_close!(
875 (gradient[0].conj() * delta).re,
876 (g.conj() * difference).re,
877 3e-6 * (1.0 + difference.norm())
878 );
879 }
880 let value = intkambe(n, z, eta).unwrap();
881 let reflected = intkambe(n, z.conj(), eta.conj()).unwrap();
882 prop_assert_close!(reflected, value.conj(), 1e-15 * value.norm());
883 Ok(())
884 }
885}