use std::f64::consts::PI;
pub(super) fn scaled_bessel_i(x: f64, nmax: usize) -> Vec<f64> {
let mut out = vec![0.0; nmax + 1];
if x <= 0.0 {
out[0] = 1.0;
return out;
}
if x <= 400.0 {
let m = 2 * ((6.0 * x.sqrt()).ceil() as usize + nmax + 10);
let half = m / 2;
for k in 0..=half {
let th = 2.0 * PI * k as f64 / m as f64;
let c = th.cos();
let w = if k == 0 || k == half { 1.0 } else { 2.0 };
let f = w * (x * (c - 1.0)).exp();
let (mut c0, mut c1) = (1.0, c);
for (n, o) in out.iter_mut().enumerate() {
let cn = if n == 0 { 1.0 } else { c1 };
*o += f * cn;
if n >= 1 {
let c2 = 2.0 * c * c1 - c0;
c0 = c1;
c1 = c2;
}
}
}
for o in &mut out {
*o /= m as f64;
}
return out;
}
let pre = 1.0 / (2.0 * PI * x).sqrt();
for (n, o) in out.iter_mut().enumerate() {
let mu = 4.0 * (n * n) as f64;
let mut term = 1.0;
let mut sum = 1.0;
for k in 1..40 {
let j = (2 * k - 1) as f64;
term *= -(mu - j * j) / (k as f64 * 8.0 * x);
sum += term;
if term.abs() < 1e-17 {
break;
}
}
*o = pre * sum;
}
out
}
#[derive(Debug, Clone)]
pub(super) struct LatticeGreen {
h: [f64; 3],
n: usize,
table: Vec<f64>,
}
impl LatticeGreen {
pub(super) fn new(h: [f64; 3], n: usize) -> Self {
let href = h.iter().copied().fold(f64::INFINITY, f64::min);
let a = h.map(|v| v / href);
let (u0, u1) = ((1e-9f64).ln(), (1e8f64).ln());
let steps = 1400usize; let du = (u1 - u0) / steps as f64;
let np = n + 1;
let mut e: [Vec<Vec<f64>>; 3] = Default::default();
let mut w = Vec::with_capacity(steps + 1);
for k in 0..=steps {
let t = (u0 + k as f64 * du).exp();
let s = if k == 0 || k == steps {
1.0
} else if k % 2 == 1 {
4.0
} else {
2.0
};
w.push(s * du / 3.0 * t);
for d in 0..3 {
e[d].push(scaled_bessel_i(2.0 * t / (a[d] * a[d]), n));
}
}
let tail = 2.0 / (4.0 * PI).powf(1.5) / (1e8f64).sqrt();
let vol = a[0] * a[1] * a[2];
let mut table = vec![0.0; np * np * np];
for i in 0..np {
for j in 0..np {
for l in 0..np {
let mut sum = 0.0;
for (k, wk) in w.iter().enumerate() {
sum += wk * e[0][k][i] * e[1][k][j] * e[2][k][l];
}
if i + j + l == 0 {
sum += u0.exp();
}
table[(i * np + j) * np + l] = (sum / vol + tail) / href;
}
}
}
Self { h, n, table }
}
pub(super) fn get(&self, d: [i64; 3]) -> f64 {
let a = d.map(|v| v.unsigned_abs() as usize);
if a.iter().all(|&v| v <= self.n) {
let np = self.n + 1;
self.table[(a[0] * np + a[1]) * np + a[2]]
} else {
let r2: f64 = (0..3).map(|k| (d[k] as f64 * self.h[k]).powi(2)).sum();
1.0 / (4.0 * PI * r2.sqrt())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bessel_sum_rule_and_values() {
for x in [0.1, 1.0, 10.0, 399.0] {
let e = scaled_bessel_i(x, 300);
let s: f64 = e[0] + 2.0 * e[1..].iter().sum::<f64>();
assert!((s - 1.0).abs() < 1e-11, "x = {x}: {s}");
}
let e = scaled_bessel_i(1.0, 2);
assert!((e[0] - 0.465_759_607_593_640_4).abs() < 1e-14);
let (a, b) = (
scaled_bessel_i(400.0, 12),
scaled_bessel_i(400.000_000_001, 12),
);
for n in 0..=12 {
assert!((a[n] / b[n] - 1.0).abs() < 1e-9, "{n}: {} {}", a[n], b[n]);
}
}
#[test]
fn watson_integral() {
let g = LatticeGreen::new([1.0; 3], 4);
assert!((g.get([0, 0, 0]) - 0.252_731_009_858_663).abs() < 1e-11);
assert!((6.0 * (g.get([0, 0, 0]) - g.get([1, 0, 0])) - 1.0).abs() < 1e-9);
let lap =
6.0 * g.get([1, 0, 0]) - g.get([0, 0, 0]) - g.get([2, 0, 0]) - 4.0 * g.get([1, 1, 0]);
assert!(lap.abs() < 1e-9, "{lap}");
let r = g.get([4, 3, 0]);
assert!((r * 4.0 * PI * 5.0 - 1.0).abs() < 5e-3, "{r}");
let g2 = LatticeGreen::new([2.0; 3], 2);
assert!((g2.get([1, 1, 0]) * 2.0 - g.get([1, 1, 0])).abs() < 1e-9);
let gr = LatticeGreen::new([1.0, 1.5, 2.0], 3);
let h2 = [1.0, 2.25, 4.0];
let lap: f64 = (0..3)
.map(|d| {
let mut p = [0i64; 3];
p[d] = 1;
2.0 * (gr.get([0, 0, 0]) - gr.get(p)) / h2[d]
})
.sum();
assert!((lap * 3.0 - 1.0).abs() < 1e-12, "{lap}");
}
}