1use std::f64::consts::{PI, TAU};
9
10use super::{
11 accuracy::Rounding,
12 reduced::{Reduced, reduced_gamma},
13 sheets::{Sheet, Split, lower_side, self_sheet, sheet_turns, split_sheet},
14 wave::{Family, azimuthal, negative_order_sign, solid_jet},
15};
16use crate::{
17 Complex, Error, Result,
18 numerics::Jet,
19 special::{self, harmonic_normalization, log_factorial},
20};
21
22fn exponential_kernel<const N: usize>(v: Jet<N>) -> Jet<N> {
25 let z = lower_side(v.value);
26 let exponential = (-z).exp();
27 let value = exponential / (-z);
28 if N == 0 {
29 return Jet::constant(value);
30 }
31 v.chain(value, (1.0 + z) * exponential / (z * z))
32}
33
34fn polynomial_sw2d<const N: usize>(
40 l: i32,
41 m: i32,
42 n: i32,
43 z: Jet<N>,
44 beta2: Jet<N>,
45 azimuth: Jet<N>,
46) -> Jet<N> {
47 let mut sum = Jet::default();
48 let first = if z.value == Complex::default() {
52 n.max(2 * n - i32::from(N > 0))
53 } else {
54 n
55 };
56 for s in first..=(l - m.abs()).min(2 * n) {
57 if (l - m.abs() - s) % 2 != 0 {
58 continue;
59 }
60 sum += (-z).powi(2 * n - s)
61 * beta2.powi((l - s - m.abs()) / 2)
62 * (-log_factorial(2 * n - s)
63 - log_factorial(s - n)
64 - log_factorial((l + m.abs() - s) / 2)
65 - log_factorial((l - m.abs() - s) / 2))
66 .exp();
67 }
68 sum * azimuth.powi(m.abs())
69}
70
71fn polynomial_sw1d<const N: usize>(
77 l: i32,
78 m: i32,
79 n: i32,
80 rho2: Jet<N>,
81 beta: Jet<N>,
82 azimuth: Jet<N>,
83) -> Jet<N> {
84 let mut sum = Jet::default();
85 for s in n..=(2 * n - m.abs()).min(l) {
86 if (m.abs() - s) % 2 != 0 {
87 continue;
88 }
89 sum += rho2.powi((2 * n - s - m.abs()) / 2)
90 * beta.powi(l - s)
91 * (-log_factorial(n - s.midpoint(m.abs()))
92 - log_factorial(n - (s - m.abs()) / 2)
93 - log_factorial(l - s)
94 - log_factorial(s - n))
95 .exp();
96 }
97 sum * azimuth.powi(m.abs())
98}
99
100fn polynomial_cw1d<const N: usize>(l: i32, n: i32, y: Jet<N>, beta: Jet<N>) -> Jet<N> {
106 let mut sum = Jet::default();
107 for s in n..=(2 * n).min(l) {
108 sum += (-y).powi(2 * n - s)
109 * beta.powi(l - s)
110 * (log_factorial(l)
111 - log_factorial(2 * n - s)
112 - log_factorial(l - s)
113 - log_factorial(s - n)
114 - f64::from(s) * 2.0_f64.ln())
115 .exp();
116 }
117 sum
118}
119
120fn axial_cylindrical_reciprocal(
126 order: i32,
127 beta: Complex,
128 z: Complex,
129 eta: Complex,
130 sheet: Option<Sheet>,
131) -> Complex {
132 let maximum = order / 2;
133 let upward = z.norm() > 4.0;
134 let mut gamma = reduced_gamma(1 - 2 * if upward { maximum } else { 0 }, z, sheet);
135 let endpoint = if maximum > 0 {
136 (if sheet.is_none() && z.im > 0.0 {
137 Complex::i()
138 } else {
139 -Complex::i()
140 }) * (-z).exp()
141 } else {
142 Complex::default()
143 };
144 let mut sum = Complex::default();
145 for step in 0..=maximum {
146 let n = if upward { maximum - step } else { step };
147 let weight = beta.powi(order - 2 * n)
148 * (0.5 * eta * eta).powi(n)
149 * (log_factorial(order) - log_factorial(n) - log_factorial(order - 2 * n)).exp()
150 / (2.0_f64.sqrt() * eta);
151 sum += weight * gamma;
152 if step < maximum {
153 let phase = if n % 2 == 0 { endpoint } else { -endpoint };
154 gamma = if upward {
155 ((0.5 - f64::from(n)) * gamma + phase) / (-z)
156 } else {
157 (-z * gamma + phase) / (-0.5 - f64::from(n))
158 };
159 }
160 }
161 sum
162}
163
164fn compensated_threshold_distance(q: [Complex; 3], k: Complex) -> Complex {
170 let (mut re, mut im) = (Compensated::default(), Compensated::default());
171 for q in q {
172 re.add_product(q.re, q.re);
173 re.add_product(-q.im, q.im);
174 im.add_product(2.0 * q.re, q.im);
175 }
176 re.add_product(-k.re, k.re);
177 re.add_product(k.im, k.im);
178 im.add_product(-2.0 * k.re, k.im);
179 Complex::new(re.total(), im.total())
180}
181
182const NEAR_THRESHOLD: f64 = 1.0 / 16.0;
187
188pub(super) const THRESHOLD_DISTANCE: f64 = 1e-14;
192
193#[derive(Clone, Copy, Debug, Default)]
196struct Compensated {
197 sum: f64,
198 error: f64,
199}
200
201impl Compensated {
202 fn add_product(&mut self, a: f64, b: f64) {
203 let product = a * b;
204 let total = self.sum + product;
205 let back = total - self.sum;
206 self.error += a.mul_add(b, -product) + (self.sum - (total - back)) + (product - back);
207 self.sum = total;
208 }
209
210 fn total(self) -> f64 {
211 self.sum + self.error
212 }
213}
214
215#[derive(Clone, Copy, Debug)]
217pub(super) struct Diffraction<const N: usize> {
218 q: [Jet<N>; 3],
219 beta2: Jet<N>,
220 value: Jet<N>,
221}
222
223impl<const N: usize> Diffraction<N> {
224 #[allow(clippy::inline_always)]
227 #[inline(always)]
228 pub(super) fn new(q: [Jet<N>; 3], exact: bool, k: Jet<N>, eta: Complex) -> Result<Self> {
229 let beta2 = q.into_iter().map(|q| q * q).sum::<Jet<N>>() / (k * k);
230 let mut value = (beta2 - 1.0) / (2.0 * eta * eta);
231 let distance = beta2.value - 1.0;
232 if exact && distance.re.abs().max(distance.im.abs()) < NEAR_THRESHOLD {
233 value.value = compensated_threshold_distance(q.map(|q| q.value), k.value)
234 / (k.value * k.value)
235 / (2.0 * eta * eta);
236 }
237 value.value = lower_side(value.value);
238 if value.value.norm() <= THRESHOLD_DISTANCE {
239 return Err(Error::InvalidInput(
240 "lattice sum is at a diffraction threshold; supply a limiting complex wavenumber"
241 .into(),
242 ));
243 }
244 Ok(Self { q, beta2, value })
245 }
246}
247
248pub(super) fn reciprocal_term<const N: usize>(
255 wave: Family,
256 dim: usize,
257 k: Jet<N>,
258 Diffraction { q, beta2, value }: Diffraction<N>,
259 r: [Jet<N>; 3],
260 eta: Complex,
261 split: &Split,
262 measure: Jet<N>,
263 bound: &mut Rounding<N>,
264 plane_integrals: Option<&mut Vec<Option<Complex>>>,
265) -> Result<Jet<N>> {
266 let i = Complex::i();
267 Ok(match (wave, dim) {
271 (Family::Spherical { l, m }, 3) => {
275 let harmonic = harmonic_normalization(l, m) * solid_jet(l, m, q);
276 2.0 * i * (-i).powi(l) * PI / (measure * k.powi(3)) * harmonic / k.powi(l)
277 * exponential_kernel(value)
278 / (eta * eta)
279 }
280 (Family::Cylindrical { m }, 2) => {
284 let xy = azimuthal(q[0], q[1], m);
285 2.0 * i * (-i).powi(m) / (measure * k * k)
286 * (xy / k).powi(m.abs())
287 * exponential_kernel(value)
288 / (eta * eta)
289 }
290 (Family::Spherical { l, m }, 2) => {
300 let z = k * r[2];
301 let azimuth = azimuthal(q[0], q[1], m) / k;
302 let factor = 1.0 / (2.0_f64.sqrt() * eta);
303 let sheet = (k.value.im >= 0.0 && (k.value * eta).re > 0.0).then(|| {
307 let q2 = q[0].value * q[0].value + q[1].value * q[1].value;
308 split_sheet(split, q2)
309 });
310 let mut reduced = Reduced::new(value, (z * eta).powi(2), sheet);
311 let orders = if z.value == Complex::default() {
314 (l - m.abs()).midpoint(i32::from(N > 0))
315 } else {
316 l - m.abs()
317 };
318 let sum = half_integer_orders(
319 &mut reduced,
320 split,
321 (factor, eta),
322 orders,
323 |n| polynomial_sw2d(l, m, n, z, beta2, azimuth),
324 plane_integrals,
325 );
326 f64::from(2 * l + 1).sqrt()
327 * (-i).powi(m)
328 * log_factorial(l + m).midpoint(log_factorial(l - m)).exp()
329 / (measure * k * k * (-2.0_f64).powi(l))
330 * sum
331 }
332 (Family::Spherical { l, m }, 1) => {
342 let beta = q[2] / k;
343 let rho2 = k * k * (r[0] * r[0] + r[1] * r[1]);
344 let azimuth = -k * azimuthal(r[0], r[1], m);
345 let mut sum = Jet::default();
346 let mult = 0.5 * eta * eta;
347 let mut factor = mult.powi(m.abs());
348 let sheet = split_sheet(split, q[2].value * q[2].value);
349 let mut reduced = Reduced::new(value, rho2 * eta * eta, Some(sheet));
350 let mut sum_bound = Rounding::default();
351 for n in m.abs()..=l {
352 let polynomial = polynomial_sw1d(l, m, n, rho2, beta, azimuth);
353 if !polynomial.is_zero() {
354 let (integral, integral_bound) = reduced.get(-2 * n);
355 sum += factor * integral * polynomial;
356 sum_bound += integral_bound.times(&(factor * polynomial));
357 }
358 factor *= mult;
359 }
360 let prefactor = -i
361 * (f64::from(2 * l + 1) / PI).sqrt()
362 * (-i).powi(l - m)
363 * log_factorial(l + m).midpoint(log_factorial(l - m)).exp()
364 / (2.0 * measure * k);
365 *bound += sum_bound.times(&prefactor);
366 prefactor * sum
367 }
368 (Family::Cylindrical { m }, 1) => {
379 let beta = q[0] / k;
380 let y = k * if m < 0 { -r[1] } else { r[1] };
381 let root = if (k.value * eta).re < 0.0 { -eta } else { eta };
387 let factor = 1.0 / (2.0_f64.sqrt() * root);
388 let sheet = (k.value.im >= 0.0).then(|| split_sheet(split, q[0].value * q[0].value));
389 if N == 0 && r[1].value == Complex::default() {
390 return Ok(Jet::constant(
391 2.0 * (-i).powi(m) / (PI.sqrt() * measure.value * k.value)
392 * axial_cylindrical_reciprocal(
393 m.abs(),
394 beta.value,
395 value.value,
396 root,
397 sheet,
398 ),
399 ));
400 }
401 let mut reduced = Reduced::new(value, (y * eta).powi(2), sheet);
402 let sum = half_integer_orders(
403 &mut reduced,
404 split,
405 (factor, eta),
406 m.abs(),
407 |n| polynomial_cw1d(m.abs(), n, y, beta),
408 None,
409 );
410 2.0 * (-i).powi(m) / (PI.sqrt() * measure * k) * sum
411 }
412 _ => return Err(Error::InvalidInput("invalid lattice dimension".into())),
413 })
414}
415
416fn half_integer_orders<const N: usize>(
422 reduced: &mut Reduced<N>,
423 split: &Split,
424 (factor, eta): (Complex, Complex),
425 orders: i32,
426 polynomial: impl Fn(i32) -> Jet<N>,
427 mut plane_integrals: Option<&mut Vec<Option<Complex>>>,
428) -> Jet<N> {
429 let mut sum = |reduced: &mut Reduced<N>| {
430 let (mut sum, mut factor) = (Jet::default(), factor);
431 for n in 0..=orders {
432 let polynomial = polynomial(n);
433 if !polynomial.is_zero() {
434 if split.small {
435 reduced.weigh(1 - 2 * n, factor * polynomial.value);
436 }
437 let integral = match plane_integrals.as_deref_mut() {
438 Some(cached) => reduced.get_plane_cached(1 - 2 * n, cached),
439 None => reduced.get(1 - 2 * n).0,
440 };
441 sum += factor * integral * polynomial;
442 }
443 factor *= 2.0 * eta * eta;
444 }
445 sum
446 };
447 let from_recurrence = sum(reduced);
448 if split.small && reduced.prefers_single() {
449 sum(reduced)
450 } else {
451 from_recurrence
452 }
453}
454
455pub(super) fn self_term<const N: usize>(
468 wave: Family,
469 k: Jet<N>,
470 r: [Jet<N>; 3],
471 eta: Complex,
472) -> Jet<N> {
473 let value = lower_side(-0.5 / (eta * eta));
474 let turns = sheet_turns(self_sheet(k.value, eta), value);
475 match wave {
476 Family::Spherical { l, m } => {
477 let degree = -f64::from(l) - 0.5;
478 let mut gamma = special::upper_gamma(degree, value);
479 if turns % 2.0 != 0.0 {
480 gamma = 2.0 * libm::tgamma(degree) - gamma;
481 }
482 k.powi(l)
483 * solid_jet(l, m, r)
484 * harmonic_normalization(l, m)
485 * (2.0_f64.powi(-l - 1) / PI.sqrt())
486 * gamma
487 }
488 Family::Cylindrical { m } => {
489 let (xy, sign) = (azimuthal(r[0], r[1], m), negative_order_sign(m));
490 let n = m.abs();
491 let parity = if n % 2 == 0 { 1.0 } else { -1.0 };
492 let gamma = special::upper_gamma(-f64::from(n), value)
493 - Complex::new(0.0, TAU * turns) * parity * (-log_factorial(n)).exp();
494 Complex::i() / PI * sign * (0.5 * k * xy).powi(n) * gamma
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::polynomial_sw2d;
502 use crate::{Complex, numerics::Jet, special::log_factorial};
503
504 fn full_polynomial<const N: usize>(
507 l: i32,
508 m: i32,
509 n: i32,
510 z: Jet<N>,
511 beta2: Jet<N>,
512 azimuth: Jet<N>,
513 ) -> Jet<N> {
514 let mut sum = Jet::default();
515 for s in n..=(l - m.abs()).min(2 * n) {
516 if (l - m.abs() - s) % 2 != 0 {
517 continue;
518 }
519 sum += (-z).powi(2 * n - s)
520 * beta2.powi((l - s - m.abs()) / 2)
521 * (-log_factorial(2 * n - s)
522 - log_factorial(s - n)
523 - log_factorial((l + m.abs() - s) / 2)
524 - log_factorial((l - m.abs() - s) / 2))
525 .exp();
526 }
527 sum * azimuth.powi(m.abs())
528 }
529
530 fn check_polynomials<const N: usize>() {
531 for z in [0.0, 1e-20, -1e-20, 0.3] {
532 let z = Jet::<N>::variable(z, 0);
533 let beta2 = Jet::variable(Complex::new(0.73, 0.19), 1);
534 let azimuth = Jet::variable(Complex::new(-0.41, 0.24), 2);
535 for l in 0_i32..=12 {
536 for m in -l..=l {
537 for n in 0..=l - m.abs() {
538 let value = polynomial_sw2d(l, m, n, z, beta2, azimuth);
539 let reference = full_polynomial(l, m, n, z, beta2, azimuth);
540 assert_eq!(value.value, reference.value, "l={l}, m={m}, n={n}");
541 assert_eq!(
542 value.derivative, reference.derivative,
543 "l={l}, m={m}, n={n}"
544 );
545 }
546 }
547 }
548 }
549 }
550
551 #[test]
552 fn planar_polynomials_keep_the_full_values_and_gradients() {
553 check_polynomials::<0>();
554 check_polynomials::<3>();
555 }
556}