#[cfg(feature = "rayon")]
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use crate::__impl_node_rule;
use core::num::NonZeroU32;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Simpson {
degree: NonZeroU32,
}
impl Simpson {
pub const fn new(degree: NonZeroU32) -> Self {
Self { degree }
}
pub fn integrate<F>(&self, a: f64, b: f64, mut integrand: F) -> f64
where
F: FnMut(f64) -> f64,
{
let n = self.iter().count() as f64;
let h = (b - a) / n;
let sum_over_interval_edges: f64 = self
.iter()
.skip(1)
.map(|node| integrand(a + node * h))
.sum();
let sum_over_midpoints: f64 = self
.iter()
.skip(1)
.map(|node| integrand(a + (2.0 * node - 1.0) * h / 2.0))
.sum();
h / 6.0
* (2.0 * sum_over_interval_edges
+ 4.0 * sum_over_midpoints
+ 4.0 * integrand(a + (2.0 * n - 1.0) * h / 2.0)
+ integrand(a)
+ integrand(b))
}
#[cfg(feature = "rayon")]
pub fn par_integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
where
F: Fn(f64) -> f64 + Sync,
{
let n = f64::from(self.degree().get());
let h = (b - a) / n;
let (sum_over_interval_edges, sum_over_midpoints): (f64, f64) = rayon::join(
|| {
(1..self.degree().get())
.into_par_iter()
.map(|node| integrand(a + f64::from(node) * h))
.sum::<f64>()
},
|| {
(1..self.degree().get())
.into_par_iter()
.map(|node| integrand(a + (2.0 * f64::from(node) - 1.0) * h / 2.0))
.sum::<f64>()
},
);
h / 6.0
* (2.0 * sum_over_interval_edges
+ 4.0 * sum_over_midpoints
+ 4.0 * integrand(a + (2.0 * n - 1.0) * h / 2.0)
+ integrand(a)
+ integrand(b))
}
pub fn change_degree(&mut self, new_degree: NonZeroU32) {
self.degree = new_degree;
}
}
__impl_node_rule! {Simpson, SimpsonIter}
impl From<NonZeroU32> for Simpson {
#[inline]
fn from(degree: NonZeroU32) -> Self {
Self { degree }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_simpson_integration() {
let quad = Simpson::new(2.try_into().unwrap());
let integral = quad.integrate(0.0, 1.0, |x| x * x);
approx::assert_abs_diff_eq!(integral, 1.0 / 3.0, epsilon = 0.0001);
}
#[cfg(feature = "rayon")]
#[test]
fn par_check_simpson_integration() {
let quad = Simpson::new(2.try_into().unwrap());
let integral = quad.par_integrate(0.0, 1.0, |x| x * x);
approx::assert_abs_diff_eq!(integral, 1.0 / 3.0, epsilon = 0.0001);
}
#[test]
fn check_derives() {
let quad = Simpson::new(10.try_into().unwrap());
let quad_clone = quad.clone();
assert_eq!(quad, quad_clone);
let other_quad = Simpson::new(3.try_into().unwrap());
assert_ne!(quad, other_quad);
}
#[test]
fn verify_from_equivalence() {
let new = Simpson::new(100.try_into().unwrap());
let from = Simpson::from(NonZeroU32::new(100).unwrap());
assert_eq!(new, from);
}
#[test]
fn test_change_degree() {
let mut quad = Simpson::new(1000.try_into().unwrap());
quad.change_degree(2000.try_into().unwrap());
assert_eq!(quad.degree(), 2000.try_into().unwrap());
}
}