use crate::{
ComplexScalar, Contour, ContourPiece, ContourSegment, FallibleIntegrable, IntegrableFloat,
IntegrationOutput, IntegrationState, IntegrationSummary, IntegratorConfig, LineSegment,
core::{GaussKronrod, IntegratorError, PathKey},
};
use nalgebra::ComplexField;
use num_traits::{Float, FromPrimitive};
use std::ops::{AddAssign, SubAssign};
use trellis_runner::{CancellationGuard, FallibleProcedure};
pub(crate) struct Integrator<Piece, F>
where
Piece: ContourPiece<Float = F>,
{
max_function_evaluations: usize,
store_segment_data: bool,
contour: Vec<Piece>,
inner: GaussKronrod<F>,
}
impl<Piece, F> Integrator<Piece, F>
where
Piece: ContourPiece<Float = F>,
{
fn new(contour: Vec<Piece>, config: &IntegratorConfig<F>) -> Self
where
F: Float + FromPrimitive + SubAssign + AddAssign + ComplexScalar,
{
Self {
max_function_evaluations: config.max_function_evaluations,
store_segment_data: config.store_segment_data,
contour,
inner: GaussKronrod::new(config.gk_config()),
}
}
}
impl<F> Integrator<LineSegment<F>, F>
where
F: IntegrableFloat + ComplexField<RealField = F>,
{
pub(crate) fn real_interval(start: F, end: F, config: &IntegratorConfig<F>) -> Self {
Self::new(vec![LineSegment::new(start, end)], config)
}
pub(crate) fn real_piecewise_linear(points: Vec<F>, config: &IntegratorConfig<F>) -> Self {
let pieces = points
.windows(2)
.map(|pair| LineSegment::new(pair[0], pair[1]))
.collect();
Self::new(pieces, config)
}
}
impl<F> Integrator<ContourSegment<F>, F>
where
F: IntegrableFloat,
{
pub(crate) fn complex_contour(contour: Contour<F>, config: &IntegratorConfig<F>) -> Self {
Self::new(contour.into_pieces(), config)
}
}
impl<P, Piece, F> FallibleProcedure<P> for Integrator<Piece, F>
where
P: FallibleIntegrable<Float = F>,
Piece: ContourPiece<Float = F, Input = P::Input>,
<P as FallibleIntegrable>::Output: IntegrationOutput<Piece::Input, Float = F>,
F: IntegrableFloat,
{
type Output = IntegrationSummary<P::Input, P::Output, F>;
type State = IntegrationState<Piece, P::Output, F>;
type Error = IntegratorError<P::Input, P::Error>;
const NAME: &'static str = "gauss-kronrod adaptive integrator";
fn initialise_fallible(
&self,
problem: &mut P,
state: &mut Self::State,
) -> Result<(), Self::Error> {
for (root, piece) in self.contour.iter().enumerate() {
let key = PathKey::new(root);
let segments = self.inner.integrate_piece_with_policy(
problem,
piece,
key,
self.store_segment_data,
)?;
state.record_evaluations(segments.len() * self.inner.evaluations_per_segment());
state.record_refinements(segments.len());
state.push_segments(segments)?;
}
Ok(())
}
fn step_fallible(
&self,
problem: &mut P,
state: &mut Self::State,
_guard: CancellationGuard<'_>,
) -> Result<(), Self::Error> {
if state.evaluations() >= self.max_function_evaluations {
return Err(IntegratorError::ExceededMaxFunctionEvaluations);
}
let worst_segment = state.pop_worst().ok_or(IntegratorError::NoSegments)?;
let new_segments =
self.inner
.refine_segment(problem, worst_segment, self.store_segment_data)?;
state.record_evaluations(new_segments.len() * self.inner.evaluations_per_segment());
state.record_refinements(new_segments.len());
state.push_segments(new_segments)?;
Ok(())
}
fn finalise_fallible(
&self,
_problem: &mut P,
state: &Self::State,
) -> Result<Self::Output, Self::Error> {
state.summary().ok_or(IntegratorError::NoSegments)
}
}