use crate::errors::QlResult;
use crate::fail;
use crate::math::integrals::Integrator;
use crate::types::{Real, Size};
type Weight = fn(Real) -> Real;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FilonType {
Sine,
Cosine,
}
pub struct FilonIntegral {
kind: FilonType,
t: Real,
n: Size,
}
impl FilonIntegral {
pub fn new(kind: FilonType, t: Real, intervals: Size) -> QlResult<Self> {
if intervals == 0 || !intervals.is_multiple_of(2) {
fail!("number of intervals must be even and positive, got {intervals}");
}
if !t.is_finite() || t == 0.0 {
fail!("frequency t must be finite and non-zero, got {t}");
}
Ok(FilonIntegral {
kind,
t,
n: intervals / 2,
})
}
}
impl Integrator for FilonIntegral {
fn integrate_impl<F>(&self, f: &mut F, a: Real, b: Real) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let n = self.n;
let two_n = 2 * n;
let h = (b - a) / two_n as Real;
let mut x = Vec::with_capacity(two_n + 1);
let mut xi = a;
for _ in 0..=two_n {
x.push(xi);
xi += h;
}
let v: Vec<Real> = x.iter().map(|&xi| f(xi)).collect();
let theta = self.t * h;
let theta2 = theta * theta;
let theta3 = theta2 * theta;
if !theta2.is_finite()
|| theta2 < Real::MIN_POSITIVE
|| !theta3.is_finite()
|| theta3.abs() < Real::MIN_POSITIVE
{
fail!("Filon phase t*h = {theta} is out of range for the coefficients over [{a}, {b}]");
}
let alpha =
1.0 / theta + (2.0 * theta).sin() / (2.0 * theta2) - 2.0 * theta.sin().powi(2) / theta3;
let beta = 2.0 * ((1.0 + theta.cos().powi(2)) / theta2 - (2.0 * theta).sin() / theta3);
let gamma = 4.0 * (theta.sin() / theta3 - theta.cos() / theta2);
let (f1, f2): (Weight, Weight) = match self.kind {
FilonType::Cosine => (Real::sin, Real::cos),
FilonType::Sine => (Real::cos, Real::sin),
};
let t = self.t;
let mut c_2n = v[0] * f2(t * a) - 0.5 * (v[two_n] * f2(t * b) + v[0] * f2(t * a));
let mut c_2n_1 = 0.0;
for i in 1..=n {
c_2n += v[2 * i] * f2(t * x[2 * i]);
c_2n_1 += v[2 * i - 1] * f2(t * x[2 * i - 1]);
}
let sign = if self.kind == FilonType::Cosine {
1.0
} else {
-1.0
};
Ok(
h * (alpha * (v[two_n] * f1(t * x[two_n]) - v[0] * f1(t * x[0])) * sign
+ beta * c_2n
+ gamma * c_2n_1),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::{FRAC_PI_2, PI};
#[test]
fn matches_known_filon_values() {
let intervals = [4, 8, 16, 128, 256, 1024, 2048];
let expected = [
4.55229440e-5,
4.72338540e-5,
4.72338540e-5,
4.78308678e-5,
4.78404787e-5,
4.78381120e-5,
4.78381084e-5,
];
let t = 100.0;
let o = FRAC_PI_2 / t;
let tol = 1e-12;
let cosine_f = |x: Real| (-0.5 * x).exp();
let sine_f = |x: Real| (-0.5 * (x - FRAC_PI_2 / 100.0)).exp();
for (i, &n) in intervals.iter().enumerate() {
let cosine = FilonIntegral::new(FilonType::Cosine, t, n)
.unwrap()
.integrate(cosine_f, 0.0, 2.0 * PI)
.unwrap();
let sine = FilonIntegral::new(FilonType::Sine, t, n)
.unwrap()
.integrate(sine_f, o, 2.0 * PI + o)
.unwrap();
assert!(
(cosine - expected[i]).abs() < tol,
"cosine n={n}: {cosine} vs {}",
expected[i]
);
assert!(
(sine - expected[i]).abs() < tol,
"sine n={n}: {sine} vs {}",
expected[i]
);
}
}
#[test]
fn constant_weight_matches_analytic_zero() {
let filon = FilonIntegral::new(FilonType::Cosine, 100.0, 128).unwrap();
let calculated = filon.integrate(|_| 1.0, 0.0, 2.0 * PI).unwrap();
assert!(calculated.abs() < 1e-10, "got {calculated}");
}
#[test]
fn tiny_frequency_underflowing_the_phase_errors() {
let filon = FilonIntegral::new(FilonType::Cosine, Real::MIN_POSITIVE, 4).unwrap();
assert!(filon.integrate(|x| x, 0.0, 1.0).is_err());
}
#[test]
fn rejects_invalid_configuration() {
assert!(FilonIntegral::new(FilonType::Sine, 1.0, 3).is_err());
assert!(FilonIntegral::new(FilonType::Sine, 1.0, 0).is_err());
for t in [0.0, Real::NAN, Real::INFINITY] {
assert!(
FilonIntegral::new(FilonType::Cosine, t, 4).is_err(),
"t={t}"
);
}
}
}