use crate::errors::QlResult;
use crate::fail;
use crate::math::comparison::close_enough;
use crate::math::integrals::Integrator;
use crate::types::{Real, Size};
pub struct SegmentIntegral {
intervals: Size,
}
impl SegmentIntegral {
pub fn new(intervals: Size) -> QlResult<Self> {
if intervals == 0 {
fail!("at least 1 interval needed, 0 given");
}
Ok(SegmentIntegral { intervals })
}
}
impl Integrator for SegmentIntegral {
fn integrate_impl<F>(&self, f: &mut F, a: Real, b: Real) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
if close_enough(a, b) {
return Ok(0.0);
}
let dx = (b - a) / self.intervals as Real;
let mut sum = 0.5 * (f(a) + f(b));
let end = b - 0.5 * dx;
let mut x = a + dx;
while x < end {
sum += f(x);
x += dx;
}
Ok(sum * dx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::distributions::normal::NormalDistribution;
const TOL: Real = 1e-6;
fn integral(f: impl FnMut(Real) -> Real, a: Real, b: Real) -> Real {
SegmentIntegral::new(10_000)
.unwrap()
.integrate(f, a, b)
.unwrap()
}
#[test]
fn reproduces_known_integrals() {
assert!((integral(|_| 0.0, 0.0, 1.0) - 0.0).abs() < TOL);
assert!((integral(|_| 1.0, 0.0, 1.0) - 1.0).abs() < TOL);
assert!((integral(|x| x, 0.0, 1.0) - 0.5).abs() < TOL);
assert!((integral(|x| x * x, 0.0, 1.0) - 1.0 / 3.0).abs() < TOL);
assert!((integral(|x| x.sin(), 0.0, std::f64::consts::PI) - 2.0).abs() < TOL);
assert!((integral(|x| x.cos(), 0.0, std::f64::consts::PI) - 0.0).abs() < TOL);
let g = NormalDistribution::standard();
assert!((integral(|x| g.value(x), -10.0, 10.0) - 1.0).abs() < TOL);
}
#[test]
fn degenerate_and_reversed_limits() {
let seg = SegmentIntegral::new(100).unwrap();
assert_eq!(seg.integrate(|x| x, 2.0, 2.0).unwrap(), 0.0);
assert_eq!(
seg.integrate(|_| 0.0, 1.0, 1.0 + Real::EPSILON).unwrap(),
0.0
);
assert!((seg.integrate(|x| x, 1.0, 0.0).unwrap() - (-0.5)).abs() < TOL);
}
#[test]
fn zero_intervals_rejected() {
assert!(SegmentIntegral::new(0).is_err());
}
#[test]
fn non_finite_bounds_rejected() {
let seg = SegmentIntegral::new(100).unwrap();
for &(a, b) in &[
(Real::NAN, 1.0),
(0.0, Real::NAN),
(Real::NEG_INFINITY, 0.0),
(0.0, Real::INFINITY),
] {
assert!(seg.integrate(|x| x, a, b).is_err(), "bounds [{a}, {b}]");
}
}
}