use std::f64::consts::PI;
pub fn bessel_i0(x: f64) -> f64 {
let ax = x.abs();
if ax < 3.75 {
let t = (ax / 3.75) * (ax / 3.75);
poly_eval(
t,
&[
1.000_000_000_0,
3.515_623_000e-1,
2.459_906_200e-2,
4.300_132_200e-3,
1.200_674_6e-4,
1.934_805_3e-5,
1.573_7e-7,
1.8e-9,
],
)
} else {
let t = 3.75 / ax;
let envelope = ax.exp() / ax.sqrt();
envelope
* poly_eval(
t,
&[
3.989_422_804_0e-1,
1.328_592_0e-2,
2.253_190_0e-3,
-1.575_649_0e-3,
9.162_860_0e-4,
-2.057_706_0e-4,
2.635_537_0e-5,
-1.647_633_0e-6,
3.921_900_0e-7,
],
)
}
}
fn poly_eval(x: f64, coeffs: &[f64]) -> f64 {
let mut result = 0.0;
for &c in coeffs.iter().rev() {
result = result * x + c;
}
result
}
pub fn kaiser_bessel_window(x: f64, m: usize, beta: f64) -> f64 {
let m_f = m as f64;
if x.abs() > m_f {
return 0.0;
}
let arg = 1.0 - (x / m_f) * (x / m_f);
let arg = arg.max(0.0);
bessel_i0(beta * arg.sqrt()) / bessel_i0(beta)
}
pub fn kb_correction(k: i64, n: usize, m: usize, beta: f64) -> f64 {
let m_f = m as f64;
let n_f = n as f64;
let t = 2.0 * PI * m_f * k as f64 / n_f;
let arg = beta * beta - t * t;
let i0_beta = bessel_i0(beta);
if i0_beta == 0.0 {
return 1.0;
}
let w_hat = if arg >= 0.0 {
let sqrt_arg = arg.sqrt();
if sqrt_arg.abs() < 1e-15 {
2.0 * m_f / i0_beta
} else {
2.0 * m_f * sqrt_arg.sinh() / (sqrt_arg * i0_beta)
}
} else {
let sqrt_neg = (-arg).sqrt();
if sqrt_neg.abs() < 1e-15 {
2.0 * m_f / i0_beta
} else {
2.0 * m_f * sqrt_neg.sin() / (sqrt_neg * i0_beta)
}
};
if w_hat.abs() < 1e-30 {
1.0 } else {
1.0 / w_hat
}
}
pub fn optimal_beta(m: usize, eps: f64) -> f64 {
let m_f = m as f64;
let eps_clamped = eps.max(1e-15).min(1.0);
let beta = m_f * (PI - 1.0 / (4.0 * m_f)) * (1.0 + (-2.0 * eps_clamped.ln() / (m_f * PI)).exp());
let beta_max = 2.5 * PI * m_f;
beta.min(beta_max)
}
pub fn kb_half_width(eps: f64, sigma: f64) -> usize {
let eps_clamped = eps.max(1e-15).min(1.0 - 1e-15);
let sigma_clamped = sigma.max(1.01);
let m = (-eps_clamped.ln() / (PI * (sigma_clamped - 1.0))).ceil() as usize;
m.max(2)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_bessel_i0_zero() {
assert_relative_eq!(bessel_i0(0.0), 1.0, epsilon = 1e-10);
}
#[test]
fn test_bessel_i0_even() {
for x in [0.5, 1.0, 2.0, 5.0, 10.0] {
assert_relative_eq!(bessel_i0(x), bessel_i0(-x), epsilon = 1e-10);
}
}
#[test]
fn test_bessel_i0_known_values() {
assert_relative_eq!(bessel_i0(1.0), 1.2660658778, epsilon = 1e-7);
assert_relative_eq!(bessel_i0(2.0), 2.2795853023, epsilon = 1e-7);
assert_relative_eq!(bessel_i0(5.0), 27.2398718040, epsilon = 1e-5);
}
#[test]
fn test_kb_window_at_zero() {
for &beta in &[5.0, 10.0, 13.9] {
let w = kaiser_bessel_window(0.0, 4, beta);
assert_relative_eq!(w, 1.0, epsilon = 1e-12, "beta={}", beta);
}
}
#[test]
fn test_kb_window_zero_outside_support() {
assert_eq!(kaiser_bessel_window(5.0, 4, 13.9), 0.0);
assert_eq!(kaiser_bessel_window(-5.0, 4, 13.9), 0.0);
}
#[test]
fn test_kb_window_symmetric() {
for x in [0.5, 1.0, 2.0, 3.0] {
let w_pos = kaiser_bessel_window(x, 4, 13.9);
let w_neg = kaiser_bessel_window(-x, 4, 13.9);
assert_relative_eq!(w_pos, w_neg, epsilon = 1e-14);
}
}
#[test]
fn test_kb_window_decreasing() {
let m = 6usize;
let beta = 14.0;
let mut prev = kaiser_bessel_window(0.0, m, beta);
for i in 1..=m {
let cur = kaiser_bessel_window(i as f64 * 0.5, m, beta);
assert!(cur <= prev + 1e-14, "Non-monotone at x={:.1}", i as f64 * 0.5);
prev = cur;
}
}
#[test]
fn test_kb_correction_finite_positive() {
let m = 4usize;
let n = 64usize;
let beta = optimal_beta(m, 1e-6);
for k in [-10i64, -1, 0, 1, 10] {
let c = kb_correction(k, n, m, beta);
assert!(c.is_finite(), "correction not finite at k={}", k);
assert!(c > 0.0, "correction not positive at k={}", k);
}
}
#[test]
fn test_optimal_beta_monotone_in_accuracy() {
let m = 4usize;
let beta_hi = optimal_beta(m, 1e-12);
let beta_lo = optimal_beta(m, 1e-3);
assert!(
beta_hi >= beta_lo,
"beta_hi={} beta_lo={}",
beta_hi,
beta_lo
);
}
#[test]
fn test_optimal_beta_positive() {
for m in [2usize, 4, 8, 12] {
for eps in [1e-3, 1e-6, 1e-9, 1e-12] {
let beta = optimal_beta(m, eps);
assert!(beta > 0.0 && beta.is_finite(), "beta={} m={} eps={}", beta, m, eps);
}
}
}
#[test]
fn test_kb_half_width_monotone() {
let m_hi = kb_half_width(1e-12, 2.0);
let m_lo = kb_half_width(1e-3, 2.0);
assert!(m_hi >= m_lo);
}
#[test]
fn test_kb_half_width_minimum() {
assert!(kb_half_width(0.5, 2.0) >= 2);
}
}