use crate::errors::QlResult;
use crate::math::integrals::Integrator;
use crate::types::{Real, Size};
use crate::{fail, require};
#[derive(Clone, Copy, Debug, Default)]
pub struct DiscreteTrapezoidIntegral;
impl DiscreteTrapezoidIntegral {
pub fn integrate(&self, x: &[Real], f: &[Real]) -> QlResult<Real> {
require!(
x.len() == f.len(),
"inconsistent size: x has {}, f has {}",
x.len(),
f.len()
);
let n = f.len();
if n < 2 {
return Ok(0.0);
}
let mut sum = 0.0;
for i in 0..n - 1 {
sum += (x[i + 1] - x[i]) * (f[i] + f[i + 1]);
}
Ok(0.5 * sum)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DiscreteSimpsonIntegral;
impl DiscreteSimpsonIntegral {
pub fn integrate(&self, x: &[Real], f: &[Real]) -> QlResult<Real> {
require!(
x.len() == f.len(),
"inconsistent size: x has {}, f has {}",
x.len(),
f.len()
);
let n = f.len();
if n < 2 {
return Ok(0.0);
}
let mut sum = 0.0;
let mut j = 0;
while j + 2 < n {
let dxj = x[j + 1] - x[j];
let dxjp1 = x[j + 2] - x[j + 1];
let alpha = dxjp1 * (2.0 * dxj - dxjp1);
let dd = dxj + dxjp1;
let k = dd / (6.0 * dxjp1 * dxj);
let beta = dd * dd;
let gamma = dxj * (2.0 * dxjp1 - dxj);
sum += k * (alpha * f[j] + beta * f[j + 1] + gamma * f[j + 2]);
j += 2;
}
if n.is_multiple_of(2) {
sum += 0.5 * (x[n - 1] - x[n - 2]) * (f[n - 1] + f[n - 2]);
}
Ok(sum)
}
}
fn require_grid(evaluations: Size, minimum: Size) -> QlResult<()> {
if evaluations < minimum {
fail!("at least {minimum} evaluations needed, got {evaluations}");
}
Ok(())
}
#[derive(Clone, Copy, Debug)]
pub struct DiscreteTrapezoidIntegrator {
evaluations: Size,
}
impl DiscreteTrapezoidIntegrator {
pub fn new(evaluations: Size) -> QlResult<Self> {
require_grid(evaluations, 2)?;
Ok(DiscreteTrapezoidIntegrator { evaluations })
}
}
impl Integrator for DiscreteTrapezoidIntegrator {
fn integrate_impl<F>(&self, f: &mut F, a: Real, b: Real) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let n = self.evaluations - 1;
let d = (b - a) / n as Real;
let mut x = a;
let mut sum = f(a) * 0.5;
for _ in 0..n - 1 {
x += d;
sum += f(x);
}
sum += f(b) * 0.5;
Ok(d * sum)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DiscreteSimpsonIntegrator {
evaluations: Size,
}
impl DiscreteSimpsonIntegrator {
pub fn new(evaluations: Size) -> QlResult<Self> {
require_grid(evaluations, 3)?;
Ok(DiscreteSimpsonIntegrator { evaluations })
}
}
impl Integrator for DiscreteSimpsonIntegrator {
fn integrate_impl<F>(&self, f: &mut F, a: Real, b: Real) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let n = self.evaluations - 1;
let d = (b - a) / n as Real;
let d2 = d * 2.0;
let mut sum = 0.0;
let mut x = a + d;
let mut i = 1;
while i < n {
sum += f(x);
x += d2;
i += 2;
}
sum *= 2.0;
x = a + d2;
let mut i = 2;
while i + 1 < n {
sum += f(x);
x += d2;
i += 2;
}
sum *= 2.0;
sum += f(a);
if !n.is_multiple_of(2) {
sum += 1.5 * f(b) + 2.5 * f(b - d);
} else {
sum += f(b);
}
Ok(d / 3.0 * sum)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::distributions::normal::NormalDistribution;
fn f1(x: Real) -> Real {
1.2 * x * x + 3.2 * x + 3.1
}
fn f2(x: Real) -> Real {
4.3 * (x - 2.34) * (x - 2.34) - 6.2 * (x - 2.34) + f1(2.34)
}
#[test]
fn sampled_formulae_match_expected_sums() {
let x = [1.0, 2.02, 2.34, 3.3, 4.2, 4.6];
let f = [f1(1.0), f1(2.02), f1(2.34), f2(3.3), f2(4.2), f2(4.6)];
let expected_simpson = 16.0401216 + 30.4137528 + 0.2 * f2(4.2) + 0.2 * f2(4.6);
let expected_trapezoid = 0.5 * (f1(1.0) + f1(2.02)) * 1.02
+ 0.5 * (f1(2.02) + f1(2.34)) * 0.32
+ 0.5 * (f2(2.34) + f2(3.3)) * 0.96
+ 0.5 * (f2(3.3) + f2(4.2)) * 0.9
+ 0.5 * (f2(4.2) + f2(4.6)) * 0.4;
let tol = 1e-12;
let simpson = DiscreteSimpsonIntegral.integrate(&x, &f).unwrap();
let trapezoid = DiscreteTrapezoidIntegral.integrate(&x, &f).unwrap();
assert!(
(simpson - expected_simpson).abs() < tol,
"simpson {simpson} vs {expected_simpson}"
);
assert!(
(trapezoid - expected_trapezoid).abs() < tol,
"trapezoid {trapezoid} vs {expected_trapezoid}"
);
}
#[test]
fn degenerate_grids_integrate_to_zero() {
for n in 0..2 {
let x: Vec<Real> = (0..n).map(|i| i as Real).collect();
let f = vec![1.0; n];
assert_eq!(DiscreteTrapezoidIntegral.integrate(&x, &f).unwrap(), 0.0);
assert_eq!(DiscreteSimpsonIntegral.integrate(&x, &f).unwrap(), 0.0);
}
}
#[test]
fn sampled_formulae_reject_mismatched_lengths() {
assert!(
DiscreteTrapezoidIntegral
.integrate(&[1.0, 2.0], &[1.0])
.is_err()
);
assert!(
DiscreteSimpsonIntegral
.integrate(&[1.0], &[1.0, 2.0])
.is_err()
);
}
const TOL: Real = 1e-6;
fn check_several(integrate: impl Fn(&dyn Fn(Real) -> Real, Real, Real) -> Real) {
assert!((integrate(&|_| 0.0, 0.0, 1.0) - 0.0).abs() < TOL);
assert!((integrate(&|_| 1.0, 0.0, 1.0) - 1.0).abs() < TOL);
assert!((integrate(&|x| x, 0.0, 1.0) - 0.5).abs() < TOL);
assert!((integrate(&|x| x * x, 0.0, 1.0) - 1.0 / 3.0).abs() < TOL);
assert!((integrate(&|x| x.sin(), 0.0, std::f64::consts::PI) - 2.0).abs() < TOL);
assert!((integrate(&|x| x.cos(), 0.0, std::f64::consts::PI) - 0.0).abs() < TOL);
let g = NormalDistribution::standard();
assert!((integrate(&|x| g.value(x), -10.0, 10.0) - 1.0).abs() < TOL);
}
#[test]
fn grid_simpson_reproduces_known_integrals() {
let integ = DiscreteSimpsonIntegrator::new(300).unwrap();
check_several(|f, a, b| integ.integrate(f, a, b).unwrap());
}
#[test]
fn grid_trapezoid_reproduces_known_integrals() {
let integ = DiscreteTrapezoidIntegrator::new(3000).unwrap();
check_several(|f, a, b| integ.integrate(f, a, b).unwrap());
}
#[test]
fn grid_integrators_reject_too_few_evaluations() {
assert!(DiscreteTrapezoidIntegrator::new(1).is_err());
assert!(DiscreteSimpsonIntegrator::new(2).is_err());
let simpson = DiscreteSimpsonIntegrator::new(3).unwrap();
assert!((simpson.integrate(|_| 1.0, 0.0, 1.0).unwrap() - 1.0).abs() < TOL);
}
}