use alloc::borrow::Cow;
use core::cell::UnsafeCell;
use crate::error::{IntegrationResult, RuntimeError::*};
use crate::single::algorithm::Algorithm;
use crate::single::common::{Integrand, IntegrationConfig, Points, Range};
use crate::single::qelg::ExtrapolationTable;
use crate::single::qk::{qk25, QKResult};
use crate::single::util::{
bisect, insert_sort, subrange_too_small, test_positivity, transform_point, transform_range,
IntegrandWrapper,
};
use crate::single::workspace::{SubRangeInfo, WorkSpace};
use crate::utils::CowMut;
#[cfg(not(feature = "std"))]
use crate::float::Float;
#[derive(Clone)]
pub struct QAGP<'a> {
workspace: CowMut<'a, WorkSpace>,
}
impl<'a> QAGP<'a> {
#[inline]
pub fn new() -> Self {
Self {
workspace: CowMut::Owned(WorkSpace::new()),
}
}
#[inline]
#[doc(hidden)]
pub fn with_workspace(ws: &'a mut WorkSpace) -> Self {
Self {
workspace: CowMut::Borrowed(ws),
}
}
}
#[doc(hidden)]
impl<'a> super::AlgorithmWithWorkSpace for QAGP<'a> {
fn workspace(&self) -> &WorkSpace {
&*self.workspace
}
}
impl<'a, F: Integrand> Algorithm<F> for QAGP<'a> {
#[inline]
fn integrate(
&mut self,
f: &mut F,
range: &Range,
config: &IntegrationConfig,
) -> IntegrationResult {
let transform = !range.begin.is_finite() || !range.end.is_finite();
let wrapper = UnsafeCell::new(IntegrandWrapper {
inner: f,
transform,
});
let range = if transform {
Cow::Owned(transform_range(range))
} else {
Cow::Borrowed(range)
};
let qk25 = |r: &Range| unsafe { qk25(&mut *wrapper.get(), r) };
integrate_impl(&qk25, &range, config, transform, &mut *self.workspace)
}
}
extra_traits!(QAGP<'a>);
fn integrate_impl(
qk25: &dyn Fn(&Range) -> QKResult,
range: &Range,
config: &IntegrationConfig,
transform: bool,
ws: &mut WorkSpace,
) -> IntegrationResult {
let pts = make_sorted_points(range, &config.points, transform);
let nint = pts.len() - 1;
ws.clear();
ws.reserve(usize::max(config.max_iters, pts.len()));
let (mut reseps, mut abseps, mut correc) = (0.0, 0.0, 0.0);
let mut ktmin = 0;
let (mut roundoff_type1, mut roundoff_type2, mut roundoff_type3) = (0, 0, 0);
let mut error = None;
let mut error2 = false;
let mut extrapolate = false;
let mut disallow_extrapolation = false;
let mut result0 = QKResult {
estimate: 0.,
delta: 0.,
absvalue: 0.,
asc: 0.,
};
for w in pts.windows(2) {
if (w[1] - w[0]).abs() < 100. * core::f64::MIN_POSITIVE {
continue;
}
let range = unsafe { Range::new_unchecked(w[0], w[1]) };
let result1 = qk25(&range);
if result1.estimate.is_nan() {
return IntegrationResult::new(
result0.estimate,
result0.delta,
Some(NanValueEncountered),
);
}
let current_level = (result1.delta == result1.asc && result1.delta != 0.0) as usize;
add_qkresult(&mut result0, &result1);
ws.push(SubRangeInfo::new(
range,
result1.estimate,
result1.delta,
current_level,
));
}
let mut deltasum = 0.;
for si in ws.subranges.iter_mut() {
if si.level > 0 {
si.delta = result0.delta;
}
deltasum += si.delta;
}
ws.subranges.iter_mut().for_each(|si| si.level = 0);
ws.sort_results();
let tolerance = config.tolerance.to_abs(result0.estimate.abs());
let round_off = 100. * core::f64::EPSILON * result0.absvalue;
if result0.delta <= round_off && result0.delta > tolerance {
return IntegrationResult::new(result0.estimate, result0.delta, Some(RoundoffError));
} else if result0.delta <= tolerance && result0.delta != result0.asc || result0.delta == 0.0 {
return IntegrationResult::new(result0.estimate, result0.delta, None);
} else if config.max_iters == 1 {
return IntegrationResult::new(
result0.estimate,
result0.delta,
Some(InsufficientIteration),
);
}
let mut table = ExtrapolationTable::default();
table.append(result0.estimate);
let mut area = result0.estimate;
let mut res_ext = result0.estimate;
let mut err_ext = core::f64::MAX;
let mut error_over_large_ranges = deltasum;
let mut ertest = tolerance;
for iteration in nint..=config.max_iters {
let info = ws.get();
let current_level = info.level + 1;
let (r1, r2) = bisect(&info.range);
let result1 = qk25(&r1);
let result2 = qk25(&r2);
if result1.estimate.is_nan() || result2.estimate.is_nan() {
error = Some(NanValueEncountered);
break;
}
let area12 = result1.estimate + result2.estimate;
let error12 = result1.delta + result2.delta;
let last_e_i = info.delta;
deltasum += error12 - info.delta;
area += area12 - info.estimate;
let tolerance = config.tolerance.to_abs(area.abs());
if result1.asc != result1.delta && result2.asc != result2.delta {
if (info.estimate - area12).abs() <= 1e-5 * area12.abs() && error12 >= 0.99 * info.delta
{
if !extrapolate {
roundoff_type1 += 1;
} else {
roundoff_type2 += 1;
}
}
if iteration > 10 && error12 > info.delta {
roundoff_type3 += 1;
}
}
if roundoff_type1 + roundoff_type2 >= 10 || roundoff_type3 >= 20 {
error = Some(RoundoffError);
}
if roundoff_type2 >= 5 {
error2 = true;
}
if subrange_too_small(r1.begin, r1.end, r2.end) {
error = Some(SubrangeTooSmall);
}
if deltasum <= tolerance {
return IntegrationResult::new(
ws.sum_results() - info.estimate + result1.estimate + result2.estimate,
deltasum,
error,
);
}
ws.update(
SubRangeInfo::new(r1, result1.estimate, result1.delta, current_level),
SubRangeInfo::new(r2, result2.estimate, result2.delta, current_level),
);
if error.is_some() {
break;
}
if iteration >= config.max_iters - 1 {
error = Some(InsufficientIteration);
break;
}
if disallow_extrapolation {
continue;
}
error_over_large_ranges += -last_e_i;
if current_level < ws.maximum_level {
error_over_large_ranges += error12;
}
if !extrapolate {
if ws.get().level < ws.maximum_level() {
continue;
}
extrapolate = true;
ws.nrmax = 1;
}
if !error2 && error_over_large_ranges > ertest && ws.increase_nrmax() {
continue;
}
table.append(area);
if table.n < 3 {
ws.reset_nrmax();
extrapolate = false;
error_over_large_ranges = deltasum;
continue;
}
table.qelg(&mut reseps, &mut abseps);
ktmin += 1;
if ktmin > 5 && err_ext < 0.001 * deltasum {
error = Some(RoundoffError);
}
if abseps < err_ext {
ktmin = 0;
err_ext = abseps;
res_ext = reseps;
correc = error_over_large_ranges;
ertest = config.tolerance.to_abs(reseps.abs());
if err_ext <= ertest {
break;
}
}
if table.n == 1 {
disallow_extrapolation = true;
}
if error.is_some() {
break;
}
ws.reset_nrmax();
extrapolate = false;
error_over_large_ranges = deltasum;
}
if err_ext == core::f64::MAX {
return IntegrationResult::new(ws.sum_results(), deltasum, error);
}
if error.is_some() || error2 {
if error2 {
err_ext += correc;
}
if error.is_none() {
error = Some(RoundoffError);
}
if res_ext != 0. && area != 0. {
if err_ext / res_ext.abs() > deltasum / area.abs() {
return IntegrationResult::new(ws.sum_results(), deltasum, error);
}
} else if err_ext > deltasum {
return IntegrationResult::new(ws.sum_results(), deltasum, error);
} else if area == 0.0 {
return IntegrationResult::new(res_ext, err_ext, error);
}
}
let positive_integrand = test_positivity(result0.estimate, result0.absvalue);
if !positive_integrand && f64::max(res_ext.abs(), area.abs()) < 0.01 * result0.absvalue {
return IntegrationResult::new(res_ext, err_ext, error);
}
let ratio = res_ext / area;
if (ratio < 0.01 || ratio > 100. || deltasum > area.abs()) && error.is_none() {
error = Some(Divergent);
}
IntegrationResult::new(res_ext, err_ext, error)
}
fn make_sorted_points(range: &Range, pts: &[f64], transform: bool) -> Points {
let (min, max) = if range.begin < range.end {
(range.begin, range.end)
} else {
(range.end, range.begin)
};
let mut pts2 = Points::with_capacity(pts.len() + 2);
pts2.push(range.begin);
if transform {
pts2.extend(pts.iter().map(|v| transform_point(*v)));
} else {
pts2.extend_from_slice(pts);
}
if range.begin < range.end {
insert_sort(&mut pts2[1..], &mut |a, b| a < b);
} else {
insert_sort(&mut pts2[1..], &mut |a, b| a > b);
};
pts2.retain(|&mut x| min <= x && x <= max);
pts2.push(range.end);
pts2
}
fn add_qkresult(result1: &mut QKResult, result2: &QKResult) {
result1.estimate += result2.estimate;
result1.delta += result2.delta;
result1.absvalue += result2.absvalue;
result1.asc += result2.asc;
}