use num_traits::Zero;
use std::collections::binary_heap::BinaryHeap;
use crate::ScalarF64;
use crate::quadrature::{Integrator, Region, Rule};
use crate::{
InitialisationError, IntegralEstimate, Integrand, IntegrationError, IntegrationErrorKind,
Limits, Tolerance,
};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Adaptive<'a, I> {
function: &'a I,
rule: &'a Rule,
limits: Limits,
tolerance: Tolerance,
max_iterations: usize,
}
impl<'a, I> Adaptive<'a, I>
where
I: Integrand<Point = f64>,
{
pub fn new(
function: &'a I,
rule: &'a Rule,
limits: Limits,
tolerance: Tolerance,
max_iterations: usize,
) -> Result<Self, InitialisationError> {
tolerance.check()?;
Ok(Self {
function,
rule,
limits,
tolerance,
max_iterations,
})
}
pub fn integrate(&self) -> Result<IntegralEstimate<I::Scalar>, IntegrationError<I::Scalar>> {
let initial = Integrator::new(self.function, self.rule, self.limits).integrate();
if let Some(output) = self.check_initial_integration(&initial)? {
return Ok(output);
}
let mut workspace = self.initialise_workspace(initial);
while workspace.iteration < self.max_iterations {
let previous = workspace.retrieve_largest_error()?;
let [lower, upper] = previous.bisect(self.function, self.rule);
let (result, error) = workspace.improved_result_error(&previous, &lower, &upper);
let iteration_tolerance = self.tolerance.tolerance(&result);
workspace.push(lower);
workspace.push(upper);
if error <= iteration_tolerance {
break;
}
workspace.check_roundoff()?;
workspace.check_singularity()?;
}
let final_iteration = workspace.iteration;
let output = workspace.integral_estimate();
if final_iteration == self.max_iterations {
let kind = IntegrationErrorKind::MaximumIterationsReached(self.max_iterations);
Err(IntegrationError::new(output, kind))
} else {
Ok(output)
}
}
#[must_use]
pub const fn limits(&self) -> Limits {
self.limits
}
}
impl<I: Integrand> Adaptive<'_, I> {
fn initialise_workspace(&self, initial: Region<I::Scalar>) -> Workspace<I::Scalar> {
let mut heap = BinaryHeap::with_capacity(2 * self.max_iterations + 1);
let iteration = 1;
let result = initial.result();
let error = initial.error();
let limits = initial.limits();
let roundoff_count = 0;
let roundoff_on_high_iteration_count = 0;
let evaluations_per_integration = self.rule.evaluations();
heap.push(initial);
Workspace {
heap,
iteration,
result,
error,
limits,
roundoff_count,
roundoff_on_high_iteration_count,
evaluations_per_integration,
}
}
const fn roundoff(result_abs: f64) -> f64 {
50.0 * f64::EPSILON * result_abs
}
pub(crate) fn check_initial_integration(
&self,
initial: &Region<I::Scalar>,
) -> Result<Option<IntegralEstimate<I::Scalar>>, IntegrationError<I::Scalar>> {
let tolerance = self.tolerance.tolerance(&initial.result());
let roundoff = Self::roundoff(initial.result_abs());
if initial.error() <= roundoff && initial.error() > tolerance {
let output = initial.estimate(1, self.rule.evaluations());
let kind = IntegrationErrorKind::RoundoffErrorDetected;
Err(IntegrationError::new(output, kind))
} else if (initial.error() <= tolerance
&& (initial.error() - initial.result_asc()).abs() > f64::EPSILON)
|| initial.error() == 0.0
{
let output = initial.estimate(1, self.rule.evaluations());
Ok(Some(output))
} else if self.max_iterations == 1 {
let output = initial.estimate(1, self.rule.evaluations());
let kind = IntegrationErrorKind::MaximumIterationsReached(self.max_iterations);
Err(IntegrationError::new(output, kind))
} else {
Ok(None)
}
}
}
struct Workspace<T> {
heap: BinaryHeap<Region<T>>,
iteration: usize,
result: T,
error: f64,
limits: Limits,
roundoff_count: usize,
roundoff_on_high_iteration_count: usize,
evaluations_per_integration: usize,
}
impl<T: ScalarF64> Workspace<T> {
fn retrieve_largest_error(&mut self) -> Result<Region<T>, IntegrationError<T>> {
self.iteration += 1;
if let Some(previous) = self.pop() {
self.limits = previous.limits();
Ok(previous)
} else {
let kind = IntegrationErrorKind::UninitialisedWorkspace;
let output = IntegralEstimate::new();
Err(IntegrationError::new(output, kind))
}
}
fn pop(&mut self) -> Option<Region<T>> {
self.heap.pop()
}
fn push(&mut self, integral: Region<T>) {
self.heap.push(integral);
}
fn improved_result_error(
&mut self,
previous: &Region<T>,
lower: &Region<T>,
upper: &Region<T>,
) -> (T, f64) {
let prev_result = previous.result();
let prev_error = previous.error();
let new_result = lower.result() + upper.result();
let new_error = lower.error() + upper.error();
if (lower.result_asc() - lower.error()).abs() > f64::EPSILON
&& (upper.result_asc() - upper.error()).abs() > f64::EPSILON
{
let delta = (prev_result - new_result).abs();
if delta <= 1e-5 * new_result.abs() && new_error >= 0.99 * prev_error {
self.roundoff_count += 1;
}
if self.iteration >= 10 && new_error >= prev_error {
self.roundoff_on_high_iteration_count += 1;
}
}
self.result += new_result - prev_result;
self.error += new_error - prev_error;
let result = self.result;
let error = self.error;
(result, error)
}
fn check_roundoff(&self) -> Result<(), IntegrationError<T>> {
if self.roundoff_count >= 6 || self.roundoff_on_high_iteration_count >= 20 {
let output = self.integral_estimate();
let kind = IntegrationErrorKind::RoundoffErrorDetected;
return Err(IntegrationError::new(output, kind));
}
Ok(())
}
fn check_singularity(&self) -> Result<(), IntegrationError<T>> {
let limits = self.limits;
if limits.subinterval_too_small() {
let output = self.integral_estimate();
let kind = IntegrationErrorKind::BadIntegrandBehaviour(limits);
Err(IntegrationError::new(output, kind))
} else {
Ok(())
}
}
fn sum_results(&self) -> T {
self.heap
.iter()
.fold(<T as Zero>::zero(), |a, v| a + v.result())
}
fn integral_estimate(&self) -> IntegralEstimate<T> {
let result = self.sum_results();
let error = self.error;
let iterations = self.iteration;
let evaluations = (2 * iterations - 1) * self.evaluations_per_integration;
IntegralEstimate::new()
.with_result(result)
.with_error(error)
.with_iterations(iterations)
.with_evaluations(evaluations)
}
}