use crate::error::{IntegrateError, IntegrateResult};
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OscillatoryKernel {
Sine,
Cosine,
}
#[derive(Debug, Clone)]
pub struct FilonResult {
pub value: f64,
pub error_estimate: f64,
pub n_evals: usize,
pub n_panels: usize,
}
#[derive(Debug, Clone)]
pub struct FilonOptions {
pub n_panels: Option<usize>,
pub abs_tol: f64,
pub rel_tol: f64,
pub max_panels: usize,
}
impl Default for FilonOptions {
fn default() -> Self {
Self {
n_panels: None,
abs_tol: 1e-10,
rel_tol: 1e-10,
max_panels: 10_000,
}
}
}
fn filon_coefficients(theta: f64) -> (f64, f64, f64) {
if theta.abs() < 1e-4 {
let t2 = theta * theta;
let t4 = t2 * t2;
let t6 = t4 * t2;
let t8 = t4 * t4;
let alpha = 2.0 * t2 / 45.0 - 2.0 * t4 / 315.0 + 2.0 * t6 / 4725.0 - 2.0 * t8 / 66825.0;
let beta =
2.0 / 3.0 + 2.0 * t2 / 15.0 - 4.0 * t4 / 105.0 + 2.0 * t6 / 567.0 - 4.0 * t8 / 14175.0;
let gamma = 4.0 / 3.0 - 2.0 * t2 / 15.0 + t4 / 210.0 - t6 / 11340.0 + t8 / 748440.0;
(alpha, beta, gamma)
} else {
let sin_t = theta.sin();
let cos_t = theta.cos();
let t2 = theta * theta;
let t3 = t2 * theta;
let alpha = (t2 + theta * sin_t * cos_t - 2.0 * sin_t * sin_t) / t3;
let beta = 2.0 * (theta * (1.0 + cos_t * cos_t) - 2.0 * sin_t * cos_t) / t3;
let gamma = 4.0 * (sin_t - theta * cos_t) / t3;
(alpha, beta, gamma)
}
}
pub fn filon<F>(
f: F,
a: f64,
b: f64,
omega: f64,
kernel: OscillatoryKernel,
options: Option<FilonOptions>,
) -> IntegrateResult<FilonResult>
where
F: Fn(f64) -> f64,
{
if omega.abs() < f64::EPSILON {
return Err(IntegrateError::ValueError(
"omega must be non-zero; use standard quadrature for non-oscillatory integrals"
.to_string(),
));
}
if (b - a).abs() < f64::EPSILON {
return Ok(FilonResult {
value: 0.0,
error_estimate: 0.0,
n_evals: 0,
n_panels: 0,
});
}
let opts = options.unwrap_or_default();
match opts.n_panels {
Some(n) => filon_fixed(&f, a, b, omega, kernel, n),
None => filon_adaptive(&f, a, b, omega, kernel, &opts),
}
}
fn filon_fixed<F>(
f: &F,
a: f64,
b: f64,
omega: f64,
kernel: OscillatoryKernel,
n_panels: usize,
) -> IntegrateResult<FilonResult>
where
F: Fn(f64) -> f64,
{
if n_panels == 0 {
return Err(IntegrateError::ValueError(
"n_panels must be positive".to_string(),
));
}
let big_n = n_panels; let total_points = 2 * big_n + 1;
let h = (b - a) / (2 * big_n) as f64;
let theta = omega * h;
let (alpha, beta, gamma) = filon_coefficients(theta);
let mut fx = Vec::with_capacity(total_points);
for i in 0..total_points {
fx.push(f(a + i as f64 * h));
}
let osc_fn = match kernel {
OscillatoryKernel::Sine => f64::sin,
OscillatoryKernel::Cosine => f64::cos,
};
let mut c_even = 0.0_f64;
for j in 0..=big_n {
let i = 2 * j;
let x = a + i as f64 * h;
let endpoint_w = if j == 0 || j == big_n { 0.5 } else { 1.0 };
c_even += endpoint_w * fx[i] * osc_fn(omega * x);
}
let mut c_odd = 0.0_f64;
for j in 0..big_n {
let i = 2 * j + 1;
let x = a + i as f64 * h;
c_odd += fx[i] * osc_fn(omega * x);
}
let value = match kernel {
OscillatoryKernel::Sine => {
let f0_cos = fx[0] * (omega * a).cos();
let fn_cos = fx[2 * big_n] * (omega * b).cos();
h * (alpha * (f0_cos - fn_cos) + beta * c_even + gamma * c_odd)
}
OscillatoryKernel::Cosine => {
let f0_sin = fx[0] * (omega * a).sin();
let fn_sin = fx[2 * big_n] * (omega * b).sin();
h * (alpha * (fn_sin - f0_sin) + beta * c_even + gamma * c_odd)
}
};
Ok(FilonResult {
value,
error_estimate: 0.0,
n_evals: total_points,
n_panels: big_n,
})
}
fn filon_adaptive<F>(
f: &F,
a: f64,
b: f64,
omega: f64,
kernel: OscillatoryKernel,
opts: &FilonOptions,
) -> IntegrateResult<FilonResult>
where
F: Fn(f64) -> f64,
{
let wavelengths = omega.abs() * (b - a) / (2.0 * PI);
let init_panels = {
let base = (4.0 * wavelengths.ceil()).max(4.0) as usize;
if base % 2 != 0 {
base + 1
} else {
base
}
};
let mut n = init_panels;
let mut prev = filon_fixed(f, a, b, omega, kernel, n)?;
loop {
n *= 2;
if n > opts.max_panels {
let last = filon_fixed(f, a, b, omega, kernel, n / 2)?;
return Ok(FilonResult {
value: last.value,
error_estimate: (last.value - prev.value).abs(),
n_evals: last.n_evals,
n_panels: last.n_panels,
});
}
let curr = filon_fixed(f, a, b, omega, kernel, n)?;
let diff = (curr.value - prev.value).abs();
let scale = curr.value.abs().max(1.0);
if diff < opts.abs_tol || diff < opts.rel_tol * scale {
return Ok(FilonResult {
value: curr.value,
error_estimate: diff,
n_evals: curr.n_evals,
n_panels: curr.n_panels,
});
}
prev = curr;
}
}
pub fn filon_simpson<F>(
f: F,
a: f64,
b: f64,
omega: f64,
kernel: OscillatoryKernel,
n_panels: usize,
) -> IntegrateResult<FilonResult>
where
F: Fn(f64) -> f64,
{
if omega.abs() < f64::EPSILON {
return Err(IntegrateError::ValueError(
"omega must be non-zero for Filon quadrature".to_string(),
));
}
let n = if n_panels < 2 {
2
} else if n_panels % 2 != 0 {
n_panels + 1
} else {
n_panels
};
let h = (b - a) / n as f64;
let n_points = n + 1;
let mut fx = Vec::with_capacity(n_points);
for i in 0..n_points {
fx.push(f(a + i as f64 * h));
}
let mut value = 0.0_f64;
for i in 0..n_points {
let x = a + i as f64 * h;
let osc = match kernel {
OscillatoryKernel::Sine => (omega * x).sin(),
OscillatoryKernel::Cosine => (omega * x).cos(),
};
let simpson_w = if i == 0 || i == n {
1.0
} else if i % 2 == 1 {
4.0
} else {
2.0
};
value += simpson_w * fx[i] * osc;
}
value *= h / 3.0;
Ok(FilonResult {
value,
error_estimate: 0.0,
n_evals: n_points,
n_panels: n,
})
}
pub fn filon_clenshaw_curtis<F>(
f: F,
a: f64,
b: f64,
omega: f64,
kernel: OscillatoryKernel,
n: usize,
) -> IntegrateResult<FilonResult>
where
F: Fn(f64) -> f64,
{
if omega.abs() < f64::EPSILON {
return Err(IntegrateError::ValueError(
"omega must be non-zero".to_string(),
));
}
if n == 0 {
return Err(IntegrateError::ValueError(
"n must be at least 1".to_string(),
));
}
let half_range = (b - a) / 2.0;
let mid = (a + b) / 2.0;
let omega_ref = omega * half_range;
let nn = n;
let mut f_vals = Vec::with_capacity(nn + 1);
for j in 0..=nn {
let t = (PI * j as f64 / nn as f64).cos();
let x = mid + half_range * t;
f_vals.push(f(x));
}
let mut coeffs = vec![0.0_f64; nn + 1];
for k in 0..=nn {
let mut s = 0.0_f64;
for j in 0..=nn {
let c_j = if j == 0 || j == nn { 0.5 } else { 1.0 };
s += c_j * f_vals[j] * (PI * k as f64 * j as f64 / nn as f64).cos();
}
let c_k = if k == 0 || k == nn { 1.0 } else { 2.0 };
coeffs[k] = c_k * s / nn as f64;
}
let moments = chebyshev_oscillatory_moments(omega_ref, nn, kernel)?;
let _ = moments; let sin_mid = (omega * mid).sin();
let cos_mid = (omega * mid).cos();
let moments_sin = chebyshev_oscillatory_moments(omega_ref, nn, OscillatoryKernel::Sine)?;
let moments_cos = chebyshev_oscillatory_moments(omega_ref, nn, OscillatoryKernel::Cosine)?;
let mut integral_sin = 0.0_f64;
let mut integral_cos = 0.0_f64;
for k in 0..=nn {
integral_sin += coeffs[k] * moments_sin[k];
integral_cos += coeffs[k] * moments_cos[k];
}
let value = match kernel {
OscillatoryKernel::Sine => half_range * (sin_mid * integral_cos + cos_mid * integral_sin),
OscillatoryKernel::Cosine => half_range * (cos_mid * integral_cos - sin_mid * integral_sin),
};
Ok(FilonResult {
value,
error_estimate: 0.0,
n_evals: nn + 1,
n_panels: nn,
})
}
fn chebyshev_oscillatory_moments(
w: f64,
n: usize,
kernel: OscillatoryKernel,
) -> IntegrateResult<Vec<f64>> {
let mut mu = vec![0.0_f64; n + 1];
if w.abs() < 1e-14 {
match kernel {
OscillatoryKernel::Cosine => {
for k in 0..=n {
if k % 2 == 0 {
let kf = k as f64;
mu[k] = 2.0 / (1.0 - kf * kf);
}
}
}
OscillatoryKernel::Sine => {
}
}
return Ok(mu);
}
let gl_n = 64;
let (gl_nodes, gl_weights) = gauss_legendre_nodes_weights(gl_n);
for k in 0..=n {
let mut s = 0.0_f64;
for i in 0..gl_n {
let t = gl_nodes[i];
let tk = chebyshev_t(k, t);
let osc = match kernel {
OscillatoryKernel::Sine => (w * t).sin(),
OscillatoryKernel::Cosine => (w * t).cos(),
};
s += gl_weights[i] * tk * osc;
}
mu[k] = s;
}
Ok(mu)
}
fn chebyshev_t(k: usize, x: f64) -> f64 {
if k == 0 {
return 1.0;
}
if k == 1 {
return x;
}
let mut t0 = 1.0_f64;
let mut t1 = x;
for _ in 2..=k {
let t2 = 2.0 * x * t1 - t0;
t0 = t1;
t1 = t2;
}
t1
}
fn gauss_legendre_nodes_weights(n: usize) -> (Vec<f64>, Vec<f64>) {
let mut nodes = vec![0.0_f64; n];
let mut weights = vec![0.0_f64; n];
let m = n.div_ceil(2);
for i in 0..m {
let mut z = (PI * (i as f64 + 0.75) / (n as f64 + 0.5)).cos();
for _ in 0..100 {
let mut p0 = 1.0_f64;
let mut p1 = z;
for k in 2..=n {
let kf = k as f64;
let p2 = ((2.0 * kf - 1.0) * z * p1 - (kf - 1.0) * p0) / kf;
p0 = p1;
p1 = p2;
}
let nf = n as f64;
let dp = nf * (z * p1 - p0) / (z * z - 1.0);
let delta = p1 / dp;
z -= delta;
if delta.abs() < 1e-15 {
break;
}
}
nodes[i] = -z;
nodes[n - 1 - i] = z;
let mut p0 = 1.0_f64;
let mut p1 = z;
for k in 2..=n {
let kf = k as f64;
let p2 = ((2.0 * kf - 1.0) * z * p1 - (kf - 1.0) * p0) / kf;
p0 = p1;
p1 = p2;
}
let nf = n as f64;
let dp = nf * (z * p1 - p0) / (z * z - 1.0);
let w = 2.0 / ((1.0 - z * z) * dp * dp);
weights[i] = w;
weights[n - 1 - i] = w;
}
(nodes, weights)
}
pub fn levin_collocation<F>(
f: F,
a: f64,
b: f64,
omega: f64,
kernel: OscillatoryKernel,
n: usize,
) -> IntegrateResult<FilonResult>
where
F: Fn(f64) -> f64,
{
if omega.abs() < f64::EPSILON {
return Err(IntegrateError::ValueError(
"omega must be non-zero".to_string(),
));
}
if n < 2 {
return Err(IntegrateError::ValueError(
"n must be at least 2 for Levin collocation".to_string(),
));
}
let half = (b - a) / 2.0;
let mid = (a + b) / 2.0;
let mut nodes = Vec::with_capacity(n);
for j in 0..n {
let t = (PI * (2 * j + 1) as f64 / (2 * n) as f64).cos();
nodes.push(mid + half * t);
}
let size = 2 * n;
let mut mat = vec![0.0_f64; size * size];
let mut rhs = vec![0.0_f64; size];
for j in 0..n {
let x = nodes[j];
let t = (x - mid) / half;
for k in 0..n {
let phi_k = t.powi(k as i32);
let dphi_k = if k == 0 {
0.0
} else {
k as f64 * t.powi(k as i32 - 1) / half
};
mat[j * size + k] = dphi_k;
mat[j * size + n + k] = -omega * phi_k;
mat[(n + j) * size + k] = omega * phi_k;
mat[(n + j) * size + n + k] = dphi_k;
}
rhs[j] = f(x);
rhs[n + j] = 0.0;
}
let coeffs = solve_linear_system(&mat, &rhs, size)?;
let eval_poly = |t: f64, start: usize| -> f64 {
let mut val = 0.0_f64;
for k in 0..n {
val += coeffs[start + k] * t.powi(k as i32);
}
val
};
let ta = (a - mid) / half;
let tb = (b - mid) / half;
let pr_a = eval_poly(ta, 0);
let pi_a = eval_poly(ta, n);
let pr_b = eval_poly(tb, 0);
let pi_b = eval_poly(tb, n);
let cos_a = (omega * a).cos();
let sin_a = (omega * a).sin();
let cos_b = (omega * b).cos();
let sin_b = (omega * b).sin();
let real_b = pr_b * cos_b - pi_b * sin_b;
let real_a = pr_a * cos_a - pi_a * sin_a;
let imag_b = pr_b * sin_b + pi_b * cos_b;
let imag_a = pr_a * sin_a + pi_a * cos_a;
let value = match kernel {
OscillatoryKernel::Cosine => real_b - real_a,
OscillatoryKernel::Sine => imag_b - imag_a,
};
Ok(FilonResult {
value,
error_estimate: 0.0,
n_evals: n,
n_panels: n,
})
}
fn solve_linear_system(a: &[f64], b: &[f64], n: usize) -> IntegrateResult<Vec<f64>> {
let mut aug = vec![0.0_f64; n * (n + 1)];
for i in 0..n {
for j in 0..n {
aug[i * (n + 1) + j] = a[i * n + j];
}
aug[i * (n + 1) + n] = b[i];
}
for col in 0..n {
let mut max_val = aug[col * (n + 1) + col].abs();
let mut max_row = col;
for row in (col + 1)..n {
let val = aug[row * (n + 1) + col].abs();
if val > max_val {
max_val = val;
max_row = row;
}
}
if max_val < 1e-14 {
return Err(IntegrateError::LinearSolveError(
"Singular or nearly singular matrix in Levin collocation".to_string(),
));
}
if max_row != col {
for j in 0..=n {
let tmp = aug[col * (n + 1) + j];
aug[col * (n + 1) + j] = aug[max_row * (n + 1) + j];
aug[max_row * (n + 1) + j] = tmp;
}
}
let pivot = aug[col * (n + 1) + col];
for row in (col + 1)..n {
let factor = aug[row * (n + 1) + col] / pivot;
for j in col..=n {
let val = aug[col * (n + 1) + j];
aug[row * (n + 1) + j] -= factor * val;
}
}
}
let mut x = vec![0.0_f64; n];
for i in (0..n).rev() {
let mut s = aug[i * (n + 1) + n];
for j in (i + 1)..n {
s -= aug[i * (n + 1) + j] * x[j];
}
x[i] = s / aug[i * (n + 1) + i];
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filon_sin_high_frequency() {
let omega: f64 = 100.0;
let exact = (1.0 - (omega * 1.0).cos()) / omega;
let result = filon(
|_x| 1.0,
0.0,
1.0,
omega,
OscillatoryKernel::Sine,
Some(FilonOptions {
n_panels: Some(200),
..Default::default()
}),
)
.expect("filon should succeed");
assert!(
(result.value - exact).abs() < 1e-6,
"Filon sin(100x): got {}, expected {}, diff={}",
result.value,
exact,
(result.value - exact).abs()
);
}
#[test]
fn test_filon_x2_cos50x() {
let omega: f64 = 50.0;
let s = (omega).sin();
let c = (omega).cos();
let exact = s / omega + 2.0 * c / (omega * omega) - 2.0 * s / (omega * omega * omega);
let result = filon(
|x| x * x,
0.0,
1.0,
omega,
OscillatoryKernel::Cosine,
Some(FilonOptions {
n_panels: Some(200),
..Default::default()
}),
)
.expect("filon should succeed");
assert!(
(result.value - exact).abs() < 1e-4,
"Filon x^2 cos(50x): got {}, expected {}, diff={}",
result.value,
exact,
(result.value - exact).abs()
);
}
#[test]
fn test_filon_convergence() {
let omega: f64 = 30.0;
let exact = (1.0 - (omega).cos()) / omega;
let mut prev_err = f64::INFINITY;
for &np in &[20, 40, 80, 200] {
let result = filon(
|_x| 1.0,
0.0,
1.0,
omega,
OscillatoryKernel::Sine,
Some(FilonOptions {
n_panels: Some(np),
..Default::default()
}),
)
.expect("filon should succeed");
let err = (result.value - exact).abs();
assert!(
err < prev_err * 1.5 || err < 1e-12,
"Convergence failed at n={}: err={}, prev_err={}",
np,
err,
prev_err
);
prev_err = err;
}
assert!(prev_err < 1e-8, "Final error too large: {}", prev_err);
}
#[test]
fn test_filon_simpson_basic() {
let omega: f64 = 10.0;
let exact = (1.0 - (omega).cos()) / omega;
let result = filon_simpson(|_x| 1.0, 0.0, 1.0, omega, OscillatoryKernel::Sine, 100)
.expect("filon_simpson should succeed");
assert!(
(result.value - exact).abs() < 1e-6,
"Filon-Simpson: got {}, expected {}, diff={}",
result.value,
exact,
(result.value - exact).abs()
);
}
#[test]
fn test_filon_clenshaw_curtis_cos() {
let omega: f64 = 20.0;
let exact = (omega).sin() / omega;
let result =
filon_clenshaw_curtis(|_x| 1.0, 0.0, 1.0, omega, OscillatoryKernel::Cosine, 32)
.expect("filon_cc should succeed");
assert!(
(result.value - exact).abs() < 1e-6,
"Filon-CC cos: got {}, expected {}, diff={}",
result.value,
exact,
(result.value - exact).abs()
);
}
#[test]
fn test_filon_clenshaw_curtis_with_amplitude() {
let omega: f64 = 30.0;
let exact = -(omega).cos() / omega + (omega).sin() / (omega * omega);
let result = filon_clenshaw_curtis(|x| x, 0.0, 1.0, omega, OscillatoryKernel::Sine, 48)
.expect("filon_cc should succeed");
assert!(
(result.value - exact).abs() < 1e-5,
"Filon-CC x*sin(30x): got {}, expected {}, diff={}",
result.value,
exact,
(result.value - exact).abs()
);
}
#[test]
fn test_levin_basic() {
let omega: f64 = 10.0;
let exact = (1.0 - (omega).cos()) / omega;
let result = levin_collocation(|_x| 1.0, 0.0, 1.0, omega, OscillatoryKernel::Sine, 8)
.expect("levin should succeed");
assert!(
(result.value - exact).abs() < 1e-4,
"Levin sin(10x): got {}, expected {}, diff={}",
result.value,
exact,
(result.value - exact).abs()
);
}
#[test]
fn test_filon_zero_interval() {
let result = filon(|_x| 1.0, 1.0, 1.0, 10.0, OscillatoryKernel::Sine, None)
.expect("zero interval should succeed");
assert!(
result.value.abs() < 1e-15,
"Zero interval should give 0, got {}",
result.value
);
}
#[test]
fn test_filon_omega_zero_error() {
let result = filon(|_x| 1.0, 0.0, 1.0, 0.0, OscillatoryKernel::Sine, None);
assert!(result.is_err(), "omega=0 should return error");
}
#[test]
fn test_filon_exp_decay_oscillatory() {
let omega: f64 = 100.0;
let w = omega;
let w2 = w * w;
let e10 = (-10.0_f64).exp();
let exact = (w - e10 * (w * (w * 10.0).cos() + (w * 10.0).sin()) - (w - 0.0)) / (1.0 + w2);
let exact2 = e10 * (-(w * 10.0).sin() - w * (w * 10.0).cos()) / (1.0 + w2) + w / (1.0 + w2);
let result = filon(
|x| (-x).exp(),
0.0,
10.0,
omega,
OscillatoryKernel::Sine,
None,
)
.expect("filon should succeed");
let _ = exact; assert!(
(result.value - exact2).abs() < 1e-5,
"Filon exp(-x)*sin(100x): got {}, expected {}, diff={}",
result.value,
exact2,
(result.value - exact2).abs()
);
}
}