1use std::f64::consts::PI;
7
8use super::{
9 accuracy::Rounding,
10 wave::{Family, azimuthal, negative_order_sign, solid_jet},
11};
12use crate::{
13 Complex,
14 numerics::Jet,
15 special::{self, SmallSplitKambe, harmonic_normalization},
16};
17
18#[derive(Debug)]
20pub(super) enum RealKambe<'a, const N: usize> {
21 Plain,
23 SmallSplit {
31 kambe: &'a SmallSplitKambe,
32 largest: &'a Rounding<N>,
33 bound: &'a mut Rounding<N>,
34 },
35 EtaDerivative,
37}
38
39fn kambe_jet<const N: usize>(
42 n: i32,
43 z: Jet<N>,
44 eta: Complex,
45 mode: RealKambe<'_, N>,
46 factor: &Jet<N>,
47) -> Jet<N> {
48 let (value, next) = match mode {
49 RealKambe::EtaDerivative => {
50 let derivative =
51 -eta.powi(n) * (0.5 * (1.0 / (eta * eta) - z.value * z.value * eta * eta)).exp();
52 let mut jet = Jet::default();
53 if let Some(slot) = jet.derivative.first_mut() {
54 *slot = derivative;
55 }
56 return jet;
57 }
58 RealKambe::Plain => {
59 let next = (N > 0).then(|| special::kambe(n + 2, z.value, eta));
60 (special::kambe(n, z.value, eta), next)
61 }
62 RealKambe::SmallSplit {
63 kambe,
64 largest,
65 bound,
66 } => small_split_kambe(n, &z, kambe, largest, bound, factor),
67 };
68 let derivative = next.map_or_else(Complex::default, |next| -z.value * next);
69 z.chain(value, derivative)
70}
71
72pub(super) fn spherical_radial<const N: usize>(
76 l: i32,
77 k: Jet<N>,
78 r: [Jet<N>; 3],
79 eta: Complex,
80) -> Jet<N> {
81 let radius = r.into_iter().map(|r| r * r).sum::<Jet<N>>().sqrt();
82 kambe_jet(2 * l, k * radius, eta, RealKambe::Plain, &Jet::default())
83}
84
85pub(super) fn spherical_from_radial<const N: usize>(
87 l: i32,
88 m: i32,
89 k: Jet<N>,
90 r: [Jet<N>; 3],
91 radial: Jet<N>,
92) -> Jet<N> {
93 let harmonic = solid_jet(l, m, r) * harmonic_normalization(l, m);
94 let factor = -Complex::i() * (2.0 / PI).sqrt() * k.powi(l) * harmonic;
95 factor * radial
96}
97
98#[inline(never)]
107pub(super) fn small_split_kambe<const N: usize>(
108 n: i32,
109 z: &Jet<N>,
110 kambe: &SmallSplitKambe,
111 largest: &Rounding<N>,
112 bound: &mut Rounding<N>,
113 factor: &Jet<N>,
114) -> (Complex, Option<Complex>) {
115 let floor = kambe_floor(largest, factor);
116 let (value, value_bound, next) = if N == 0 {
117 let [(value, value_bound)] = kambe.get([n], z.value, floor);
118 (value, value_bound, None)
119 } else {
120 let [(value, value_bound), next] = kambe.get([n, n + 2], z.value, floor);
121 (value, value_bound, Some(next))
122 };
123 let (next_bound, modulus) = (next.map_or(0.0, |(_, bound)| bound), z.value.l1_norm());
124 *bound = Rounding {
125 value: value_bound,
126 derivative: z.derivative.map(|d| next_bound * modulus * d.l1_norm()),
127 }
128 .times(factor);
129 (value, next.map(|(next, _)| next))
130}
131
132fn kambe_floor<const N: usize>(largest: &Rounding<N>, factor: &Jet<N>) -> f64 {
138 let scale = factor.value.l1_norm();
139 if scale > 0.0 {
140 return largest.value / scale;
141 }
142 largest
143 .derivative
144 .iter()
145 .zip(&factor.derivative)
146 .filter(|&(_, &d)| d != Complex::default())
147 .map(|(largest, d)| largest / d.l1_norm())
148 .fold(f64::INFINITY, f64::min)
149}
150
151pub(super) fn real_term<const N: usize>(
163 wave: Family,
164 k: Jet<N>,
165 r: [Jet<N>; 3],
166 eta: Complex,
167 mode: RealKambe<'_, N>,
168) -> Jet<N> {
169 let radius = r.into_iter().map(|r| r * r).sum::<Jet<N>>().sqrt();
170 let (factor, order) = match wave {
171 Family::Spherical { l, m } => {
172 let harmonic = solid_jet(l, m, r) * harmonic_normalization(l, m);
173 (
174 -Complex::i() * (2.0 / PI).sqrt() * k.powi(l) * harmonic,
175 2 * l,
176 )
177 }
178 Family::Cylindrical { m } => {
179 let (xy, sign) = (azimuthal(r[0], r[1], m), negative_order_sign(m));
180 (
181 -2.0 * Complex::i() / PI * sign * (k * xy).powi(m.abs()),
182 2 * m.abs() - 1,
183 )
184 }
185 };
186 factor * kambe_jet(order, k * radius, eta, mode, &factor)
187}