use std::{
fmt,
ops::{AddAssign, MulAssign},
};
use num::Float;
use crate::utils::adaptive_simpson::{simpson_rule_update, AdaptiveSimpsonError, SubInterval};
type Result<T> = std::result::Result<T, AdaptiveSimpsonError>;
pub fn adaptive_simpson_method<Func, F: Float + MulAssign + AddAssign + fmt::Debug>(
func: Func,
lower_limit: F,
upper_limit: F,
min_h: F,
tolerance: F,
) -> Result<F>
where
Func: Fn(F) -> F + Sync + Copy,
{
assert!(
min_h > F::zero() && min_h.is_finite(),
"min_h must be positive and finite"
);
assert!(
tolerance > F::zero() && tolerance.is_finite(),
"tolerance must be positive and finite"
);
let two = F::one() + F::one();
let mut integral: F = F::zero();
let epsilon_density = two * tolerance / (upper_limit - lower_limit);
let interval: SubInterval<F> = SubInterval {
upper_limit,
lower_limit,
function: [
func(lower_limit),
F::nan(),
func((lower_limit + upper_limit) / two),
F::nan(),
func(upper_limit),
],
interval: None,
};
let mut pinterval = Box::new(interval);
let mut epsilon = epsilon_density * (upper_limit - lower_limit);
let (mut s1, mut s2) = simpson_rule_update(func, &mut pinterval);
let mut qinterval: SubInterval<F>;
while pinterval.upper_limit - pinterval.lower_limit > min_h {
if (s1 - s2).abs() < epsilon {
integral += s2;
if pinterval.interval.is_none() {
return Ok(integral);
}
qinterval = *pinterval.interval.take().unwrap();
qinterval.lower_limit = pinterval.upper_limit;
qinterval.function[0] = qinterval.function[2];
qinterval.function[2] = qinterval.function[3];
pinterval = Box::new(qinterval);
} else {
let limit1 = pinterval.lower_limit;
let limit2 = (pinterval.upper_limit + pinterval.lower_limit) / two;
let upper_limit = if limit1 > limit2 { limit1 } else { limit2 };
let lower_limit = if limit1 > limit2 { limit2 } else { limit1 };
qinterval = SubInterval {
lower_limit,
upper_limit,
function: [F::nan(); 5],
interval: None,
};
qinterval.function[0] = pinterval.function[0];
qinterval.function[2] = pinterval.function[1];
qinterval.function[4] = pinterval.function[2];
qinterval.interval = Some(pinterval);
pinterval = Box::new(qinterval);
}
(s1, s2) = simpson_rule_update(func, &mut pinterval);
epsilon = epsilon_density * (pinterval.upper_limit - pinterval.lower_limit);
}
Err(AdaptiveSimpsonError)
}