use super::csr_matvec_set;
pub fn bessel_i(k: usize, tau: f32) -> f32 {
if tau == 0.0 {
return if k == 0 { 1.0 } else { 0.0 };
}
let tau_d = tau as f64;
let k_max = (2 * k + 60).max(80);
let mut f_next = 0.0f64; let mut f_curr = 1.0f64;
let mut f_k = 0.0f64;
let mut norm_sum = 0.0f64;
let mut scale_acc = 1.0f64;
let rescale = |f_next: &mut f64,
f_curr: &mut f64,
f_k: &mut f64,
norm_sum: &mut f64,
scale_acc: &mut f64| {
let s = 1.0 / f_curr.abs().max(1e-300);
*f_next *= s;
*f_curr *= s;
*f_k *= s;
*norm_sum *= s;
*scale_acc *= s;
};
for j in (0..k_max).rev() {
let f_prev = (2.0 * (j + 1) as f64 / tau_d) * f_curr + f_next;
f_next = f_curr;
f_curr = f_prev;
if f_curr.abs() > 1e150 {
rescale(
&mut f_next,
&mut f_curr,
&mut f_k,
&mut norm_sum,
&mut scale_acc,
);
}
if j == k {
f_k = f_curr;
}
if j == 0 {
norm_sum += f_curr; } else {
norm_sum += 2.0 * f_curr; }
}
if norm_sum.abs() < 1e-300 {
return 0.0;
}
let result = f_k * tau_d.exp() / norm_sum;
result as f32
}
pub fn chebyshev_coeffs(tau: f32, order: usize) -> Vec<f32> {
let exp_neg_tau = (-tau).exp();
(0..=order)
.map(|k| {
let factor = if k == 0 { 1.0 } else { 2.0 };
let sign = if k % 2 == 0 { 1.0f32 } else { -1.0 };
exp_neg_tau * factor * sign * bessel_i(k, tau)
})
.collect()
}
#[allow(clippy::too_many_arguments)]
pub fn chebyshev_matvec(
row_ptr: &[u32],
col_idx: &[u32],
values: &[f32],
d_inv_sqrt: &[f32],
x: &[f32],
y: &mut [f32],
t0: &mut [f32],
t1: &mut [f32],
tau: f32,
order: usize,
) {
let n = x.len();
debug_assert_eq!(y.len(), n);
debug_assert_eq!(t0.len(), n);
debug_assert_eq!(t1.len(), n);
let coeffs = chebyshev_coeffs(tau, order);
let mut az = vec![0.0f32; n];
t0.copy_from_slice(x);
for i in 0..n {
az[i] = d_inv_sqrt[i] * x[i];
}
csr_matvec_set(row_ptr, col_idx, values, &az, t1);
for i in 0..n {
t1[i] = -d_inv_sqrt[i] * t1[i];
}
let c1 = if order >= 1 { coeffs[1] } else { 0.0 };
for i in 0..n {
y[i] = coeffs[0] * t0[i] + c1 * t1[i];
}
if order < 2 {
return;
}
for k in 2..=order {
let ck = coeffs[k];
if k % 2 == 0 {
for i in 0..n {
az[i] = d_inv_sqrt[i] * t1[i];
}
let mut az2 = vec![0.0f32; n];
csr_matvec_set(row_ptr, col_idx, values, &az, &mut az2);
for i in 0..n {
t0[i] = -2.0 * d_inv_sqrt[i] * az2[i] - t0[i];
}
for i in 0..n {
y[i] += ck * t0[i];
}
} else {
for i in 0..n {
az[i] = d_inv_sqrt[i] * t0[i];
}
let mut az2 = vec![0.0f32; n];
csr_matvec_set(row_ptr, col_idx, values, &az, &mut az2);
for i in 0..n {
t1[i] = -2.0 * d_inv_sqrt[i] * az2[i] - t1[i];
}
for i in 0..n {
y[i] += ck * t1[i];
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bessel_i0_known_values() {
assert!((bessel_i(0, 0.0) - 1.0).abs() < 1e-5, "I_0(0)");
assert!(
(bessel_i(0, 1.0) - 1.266_065_9).abs() < 1e-4,
"I_0(1) = {}",
bessel_i(0, 1.0)
);
assert!(
(bessel_i(1, 1.0) - 0.565_159_1).abs() < 1e-4,
"I_1(1) = {}",
bessel_i(1, 1.0)
);
assert!(
(bessel_i(0, 5.0) - 27.2399).abs() < 0.05,
"I_0(5) = {}",
bessel_i(0, 5.0)
);
}
#[test]
fn chebyshev_coeffs_at_x1() {
for tau in [0.5f32, 1.0, 3.0] {
let coeffs = chebyshev_coeffs(tau, 60);
let sum: f32 = coeffs.iter().sum();
let expected = (-2.0 * tau).exp();
assert!(
(sum - expected).abs() < 0.01,
"Ξ£ c_k at tau={tau}: got {sum}, expected exp(-2Ο) = {expected}"
);
}
}
#[test]
fn chebyshev_matvec_path4_smooth() {
let row_ptr = vec![0u32, 1, 3, 5, 6];
let col_idx = vec![1u32, 0, 2, 1, 3, 2];
let values = vec![1.0f32; 6];
let n = 4;
let d_inv_sqrt = vec![1.0f32, 1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt(), 1.0];
let x = vec![1.0f32, 0.0, 0.0, 0.0];
let mut y = vec![0.0f32; n];
let mut t0 = vec![0.0f32; n];
let mut t1 = vec![0.0f32; n];
chebyshev_matvec(
&row_ptr,
&col_idx,
&values,
&d_inv_sqrt,
&x,
&mut y,
&mut t0,
&mut t1,
1.0,
20,
);
assert!(y[1] > 1e-4, "y[1] should receive diffused mass: {}", y[1]);
assert!(y[0] < 0.99, "y[0] should decrease: {}", y[0]);
assert!(y[0] > 0.0, "y[0] should remain non-negative: {}", y[0]);
let norm_y: f32 = y.iter().map(|&v| v * v).sum::<f32>().sqrt();
assert!(norm_y <= 1.01, "βyββ = {norm_y} should be β€ βxββ = 1");
}
}