use alloc::borrow::Cow;
use core::cell::UnsafeCell;
use crate::error::{IntegrationResult, RuntimeError::*};
use crate::single::algorithm::Algorithm;
use crate::single::common::{Integrand, IntegrationConfig, Range};
use crate::single::qelg::ExtrapolationTable;
use crate::single::qk::{qk17, qk25, QKResult};
use crate::single::util::{
bisect, subrange_too_small, test_positivity, 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 QAGS<'a> {
workspace: CowMut<'a, WorkSpace>,
}
impl<'a> QAGS<'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 QAGS<'a> {
fn workspace(&self) -> &WorkSpace {
&*self.workspace
}
}
impl<'a, F: Integrand> Algorithm<F> for QAGS<'a> {
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 qk17 = |r: &Range| unsafe { qk17(&mut *wrapper.get(), r) };
let qk25 = |r: &Range| unsafe { qk25(&mut *wrapper.get(), r) };
integrate_impl(&qk17, &qk25, &range, config, &mut *self.workspace)
}
}
extra_traits!(QAGS<'a>);
fn integrate_impl(
qk17: &dyn Fn(&Range) -> QKResult,
qk25: &dyn Fn(&Range) -> QKResult,
range: &Range,
config: &IntegrationConfig,
ws: &mut WorkSpace,
) -> IntegrationResult {
let mut ertest = 0f64;
let mut error_over_large_ranges = 0f64;
let mut correc = 0.;
let mut ktmin = 0usize;
let (mut roundoff_type1, mut roundoff_type2, mut roundoff_type3) = (0i32, 0i32, 0i32);
let mut error = None;
let mut error2 = 0i32;
let mut extrapolate = false;
let mut disallow_extrapolation = false;
let (result0, absvalue, finished) = initial_integral(qk17, qk25, range, config);
if finished {
return result0;
}
ws.clear();
ws.reserve(config.max_iters);
ws.push(SubRangeInfo::new(
range.clone(),
result0.estimate,
result0.delta,
0,
));
let mut table = ExtrapolationTable::default();
table.append(result0.estimate);
let mut area = result0.estimate;
let mut errsum = result0.delta;
let mut res_ext = result0.estimate;
let mut err_ext = core::f64::MAX;
for iteration in 2..=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;
errsum += 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 = 1;
}
if subrange_too_small(r1.begin, r1.end, r2.end) {
error = Some(SubrangeTooSmall);
}
if errsum <= tolerance {
return IntegrationResult::new(
ws.sum_results() - info.estimate + result1.estimate + result2.estimate,
errsum,
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 iteration == 2 {
error_over_large_ranges = errsum;
ertest = tolerance;
table.append(area);
continue;
};
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 error_over_large_ranges > ertest && ws.increase_nrmax() {
continue;
}
let (mut reseps, mut abseps) = (0., 0.);
table.append(area);
table.qelg(&mut reseps, &mut abseps);
ktmin += 1;
if ktmin > 5 && err_ext < 0.001 * errsum {
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 = errsum;
}
if err_ext == core::f64::MAX {
return IntegrationResult::new(ws.sum_results(), errsum, error);
}
if error.is_some() || error2 > 0 {
if error2 > 0 {
err_ext += correc;
}
if error.is_none() {
error = Some(RoundoffError);
}
if res_ext != 0.0 && area != 0.0 {
if err_ext / res_ext.abs() > errsum / area.abs() {
return IntegrationResult::new(ws.sum_results(), errsum, error);
}
} else if err_ext > errsum {
return IntegrationResult::new(ws.sum_results(), errsum, error);
} else if area == 0.0 {
return IntegrationResult::new(res_ext, err_ext, error);
}
}
let positive_integrand = test_positivity(result0.estimate, absvalue);
if !positive_integrand && f64::max(res_ext.abs(), area.abs()) < 0.01 * absvalue {
return IntegrationResult::new(res_ext, err_ext, error);
}
let ratio = res_ext / area;
if (ratio < 0.01 || ratio > 100.0 || errsum > area.abs()) && error.is_none() {
error = Some(Divergent);
}
IntegrationResult::new(res_ext, err_ext, error)
}
fn initial_integral(
qk17: &dyn Fn(&Range) -> QKResult,
qk25: &dyn Fn(&Range) -> QKResult,
range: &Range,
config: &IntegrationConfig,
) -> (IntegrationResult, f64, bool) {
for i in 0..2 {
let result0 = if i == 0 {
qk17(&range)
} else if i == 1 {
qk25(&range)
} else {
unreachable!();
};
if result0.estimate.is_nan() {
return (
IntegrationResult::new(result0.estimate, result0.delta, Some(NanValueEncountered)),
0.,
true,
);
}
let tolerance = config.tolerance.to_abs(result0.estimate.abs());
if result0.delta <= tolerance && result0.delta != result0.asc || result0.delta == 0.0 {
return (
IntegrationResult::new(result0.estimate, result0.delta, None),
0.,
true,
);
} else if config.max_iters == 1 {
return (
IntegrationResult::new(
result0.estimate,
result0.delta,
Some(InsufficientIteration),
),
0.,
true,
);
}
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)),
0.,
true,
);
}
if i == 1 || result0.delta > tolerance * 1024. {
return (
IntegrationResult::new(result0.estimate, result0.delta, None),
result0.absvalue,
false,
);
}
}
unreachable!();
}