use super::{AccumulateError, Generate, IntegrationError, IntegrationValues, Segment, SegmentData};
use nalgebra::RealField;
use num_traits::{Float, FromPrimitive};
mod poly;
mod pre;
mod quad;
pub(crate) use quad::GaussKronrodCore;
#[derive(Debug)]
pub enum Method {
Mean,
Max,
}
#[derive(Debug)]
pub struct GaussKronrod<F> {
m: usize,
n: usize,
pub xgk: Vec<F>,
pub wg: Vec<F>,
pub wgk: Vec<F>,
relative_tolerance: F,
absolute_tolerance: F,
maximum_number_of_function_evaluations: usize,
minimum_segment_width: F,
method: Method,
}
impl<F: FromPrimitive> Default for GaussKronrod<F> {
fn default() -> Self {
let m = 10;
let n = m + 1;
Self {
m,
n,
xgk: pre::XGK_10_F64
.into_iter()
.map(|val| F::from_f64(val).unwrap())
.collect::<Vec<_>>(),
wg: pre::WG_10_F64
.into_iter()
.map(|val| F::from_f64(val).unwrap())
.collect::<Vec<_>>(),
wgk: pre::WGK_10_F64
.into_iter()
.map(|val| F::from_f64(val).unwrap())
.collect::<Vec<_>>(),
relative_tolerance: F::from_f64(1.49e-08).unwrap(),
absolute_tolerance: F::from_f64(1.49e-08).unwrap(),
maximum_number_of_function_evaluations: 5000,
minimum_segment_width: F::from_f64(1e-8).unwrap(),
method: Method::Max,
}
}
}
impl<F> GaussKronrod<F>
where
F: Float + RealField + FromPrimitive,
{
pub fn new(m: usize) -> Self {
let n = m + 1;
let zeros = Self::compute_legendre_zeros(m);
let coeffs = Self::compute_chebyshev_coefficients(m);
let abscissae = Self::compute_gauss_kronrod_abscissae(m, &coeffs, &zeros);
let weights = Self::compute_gauss_kronrod_weights(&abscissae, &coeffs);
Self {
m,
n,
xgk: abscissae,
wg: weights.gauss,
wgk: weights.gauss_kronrod,
relative_tolerance: F::from_f64(1.49e-08).unwrap(),
absolute_tolerance: F::from_f64(1.49e-08).unwrap(),
maximum_number_of_function_evaluations: 5000,
minimum_segment_width: F::from_f64(1e-8).unwrap(),
method: Method::Max,
}
}
pub fn accumulate<E: AccumulateError<F>>(&self, error: E) -> F {
match self.method {
Method::Max => error.max(),
Method::Mean => error.mean(),
}
}
}
impl<F> Generate<F> for GaussKronrod<F>
where
F: Copy + FromPrimitive + RealField,
{
fn generate(
&self,
range: std::ops::Range<F>,
evaluation_points: Option<Vec<F>>,
target_number_of_points: usize,
) -> IntegrationValues<F> {
let number_of_points_per_segment = self.xgk.len() * 2 - 1;
let mut scaled_points = vec![F::zero(); number_of_points_per_segment];
scaled_points
.iter_mut()
.zip(self.xgk.iter())
.for_each(|(x, &y)| *x = -y);
scaled_points
.iter_mut()
.skip(self.xgk.len() - 1)
.zip(self.xgk.iter().rev())
.for_each(|(x, &y)| *x = y);
let mut gk_weights = vec![F::zero(); number_of_points_per_segment];
gk_weights
.iter_mut()
.zip(self.wgk.iter())
.for_each(|(x, &y)| *x = y);
gk_weights
.iter_mut()
.skip(self.wgk.len() - 1)
.zip(self.wgk.iter().rev())
.for_each(|(x, &y)| *x = y);
let mut points = Vec::new();
let mut weights = Vec::new();
if evaluation_points.is_none() {
let number_of_segments = (target_number_of_points - number_of_points_per_segment)
/ (number_of_points_per_segment - 1)
+ 2;
let segment_width =
(range.end - range.start) / (F::from_usize(number_of_segments).unwrap());
for idx in 0..number_of_segments {
let segment_start = F::from_usize(idx).unwrap() * segment_width;
let segment_end = segment_start + segment_width;
let segment_midpoint = (segment_start + segment_end) / F::from_f64(2.).unwrap();
let segment_half_length = (segment_end - segment_start) / F::from_f64(2.).unwrap();
let points_in_segment = scaled_points
.iter()
.map(|&x| x / F::from_f64(2.).unwrap() * segment_width + segment_midpoint)
.take(number_of_points_per_segment - 1);
let weights_in_segment = gk_weights
.iter()
.take(number_of_points_per_segment - 1)
.map(|x| *x * segment_half_length);
points.extend(points_in_segment);
weights.extend(weights_in_segment);
}
points.push(range.end);
weights.push(gk_weights[gk_weights.len() - 1]);
} else {
let e_points = evaluation_points.unwrap();
let number_of_segments = (target_number_of_points - number_of_points_per_segment)
/ (number_of_points_per_segment - 1)
+ 2
- e_points.len();
let segment_width =
(range.end - range.start) / (F::from_usize(number_of_segments).unwrap());
let mut segment_points = (0..number_of_segments)
.map(|n| F::from_usize(n).unwrap() * segment_width)
.chain(e_points)
.collect::<Vec<_>>();
segment_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
segment_points.push(range.end);
let segment_widths = segment_points
.windows(2)
.map(|x| x[1] - x[0])
.collect::<Vec<_>>();
for (segment_width, segment_start) in
segment_widths.into_iter().zip(segment_points.into_iter())
{
let segment_end = segment_start + segment_width;
let segment_midpoint = (segment_start + segment_end) / F::from_f64(2.).unwrap();
let segment_half_length = (segment_end - segment_start) / F::from_f64(2.).unwrap();
let points_in_segment = scaled_points
.iter()
.map(|&x| x / F::from_f64(2.).unwrap() * segment_width + segment_midpoint)
.take(number_of_points_per_segment - 1);
let weights_in_segment = gk_weights
.iter()
.take(number_of_points_per_segment - 1)
.map(|&x| x * segment_half_length);
points.extend(points_in_segment);
weights.extend(weights_in_segment);
}
points.push(range.end);
weights.push(gk_weights[gk_weights.len() - 1]);
}
IntegrationValues {
evaluation_points: points,
weights,
}
}
}