use crate::C64;
const NEGLIGIBLE_WEIGHT: f64 = 1e-300;
pub fn simpson(f: impl Fn(f64) -> f64, a: f64, b: f64, n: usize) -> f64 {
try_simpson(|t| Ok::<f64, std::convert::Infallible>(f(t)), a, b, n).unwrap_or_else(|never| match never {})
}
pub fn try_simpson<E>(f: impl Fn(f64) -> Result<f64, E>, a: f64, b: f64, n: usize) -> Result<f64, E> {
let n = n.max(2).div_ceil(2) * 2;
let h = (b - a) / n as f64;
let mut acc = f(a)? + f(b)?;
for k in 1..n {
acc += simpson_weight(k, n) * f(a + k as f64 * h)?;
}
Ok(acc * h / 3.0)
}
#[inline]
pub fn simpson_weight(k: usize, n: usize) -> f64 {
if k == 0 || k == n {
1.0
} else if k % 2 == 1 {
4.0
} else {
2.0
}
}
pub fn gauss_hermite(n: usize) -> (Vec<f64>, Vec<f64>) {
const PIM4: f64 = 0.751_125_544_464_942_5; let mut x = vec![0.0; n];
let mut w = vec![0.0; n];
let nf = n as f64;
let mut z = 0.0f64;
for i in 0..n.div_ceil(2) {
z = match i {
0 => (2.0 * nf + 1.0).sqrt() - 1.85575 * (2.0 * nf + 1.0).powf(-0.16667),
1 => z - 1.14 * nf.powf(0.426) / z,
2 => 1.86 * z - 0.86 * x[0],
3 => 1.91 * z - 0.91 * x[1],
_ => 2.0 * z - x[i - 2],
};
let mut pp = 1.0;
for _ in 0..200 {
let (mut p1, mut p2) = (PIM4, 0.0);
for j in 0..n {
let p3 = p2;
p2 = p1;
let jf = j as f64;
p1 = z * (2.0 / (jf + 1.0)).sqrt() * p2 - (jf / (jf + 1.0)).sqrt() * p3;
}
pp = (2.0 * nf).sqrt() * p2;
let z1 = z;
z = z1 - p1 / pp;
if (z - z1).abs() <= 1e-15 * z.abs().max(1.0) {
break;
}
}
x[i] = z;
x[n - 1 - i] = -z;
w[i] = 2.0 / (pp * pp);
w[n - 1 - i] = w[i];
}
(x, w)
}
pub fn gauss_legendre(n: usize) -> (Vec<f64>, Vec<f64>) {
let mut x = vec![0.0; n];
let mut w = vec![0.0; n];
let nf = n as f64;
for i in 0..n.div_ceil(2) {
let mut z = (std::f64::consts::PI * (i as f64 + 0.75) / (nf + 0.5)).cos();
let mut pp = 1.0;
for _ in 0..100 {
let (mut p1, mut p2) = (1.0, 0.0);
for j in 0..n {
let p3 = p2;
p2 = p1;
let jf = j as f64;
p1 = ((2.0 * jf + 1.0) * z * p2 - jf * p3) / (jf + 1.0);
}
pp = nf * (z * p1 - p2) / (z * z - 1.0);
let z1 = z;
z = z1 - p1 / pp;
if (z - z1).abs() <= 1e-15 {
break;
}
}
x[i] = -z;
x[n - 1 - i] = z;
w[i] = 2.0 / ((1.0 - z * z) * pp * pp);
w[n - 1 - i] = w[i];
}
(x, w)
}
pub fn gauss_chebyshev(n: usize) -> (Vec<f64>, Vec<f64>) {
let x = (1..=n).map(|k| ((2 * k - 1) as f64 * std::f64::consts::PI / (2 * n) as f64).cos()).collect();
(x, vec![std::f64::consts::PI / n as f64; n])
}
pub fn gaussian_nodes(noise_var: f64, complex: bool, n: usize) -> Vec<(C64, f64)> {
let (t, a) = gauss_hermite(n.max(2));
let pi = std::f64::consts::PI;
if complex {
let s = noise_var.sqrt();
t.iter()
.zip(&a)
.flat_map(|(tr, ar)| t.iter().zip(&a).map(move |(ti, ai)| (C64::new(s * tr, s * ti), ar * ai / pi)))
.filter(|(_, w)| *w > NEGLIGIBLE_WEIGHT)
.collect()
} else {
let s = (2.0 * noise_var).sqrt();
t.iter().zip(&a).map(|(t, a)| (C64::new(s * t, 0.0), a / pi.sqrt())).collect()
}
}