use crate::utils::Steps;
use crate::Complex;
use num::Integer;
use quad_rs::Integrable;
use rayon::prelude::*;
struct Problem<F, Y>(pub F)
where
F: Fn(Complex<f64>) -> Y;
impl<F, Y> Integrable for Problem<F, Y>
where
Y: Into<Complex<f64>>,
F: Fn(Complex<f64>) -> Y,
{
type Input = Complex<f64>;
type Output = Complex<f64>;
fn integrand(
&self,
input: &Self::Input,
) -> Result<Self::Output, quad_rs::EvaluationError<Self::Input>> {
Ok((self.0)(*input).into())
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(tag = "method")]
pub enum Integrator {
Simpson {
divs: usize,
},
AdaptiveSimpson {
tolerance: f64,
max_depth: usize,
},
GaussKonrod {
tolerance: f64,
max_depth: usize,
},
GaussLegendre {
degree: usize,
},
ClenshawCurtis {
tolerance: f64,
},
}
impl Default for Integrator {
fn default() -> Self {
Integrator::Simpson { divs: 50 }
}
}
impl Integrator {
pub fn integrate<F, Y>(&self, func: F, a: f64, b: f64) -> Complex<f64>
where
F: Send + Sync + Fn(f64) -> Y,
Y: Into<Complex<f64>>,
{
match self {
Integrator::Simpson { divs } => simpson(func, a, b, *divs),
Integrator::AdaptiveSimpson {
tolerance,
max_depth,
} => simpson_adaptive(&func, a, b, *tolerance, *max_depth),
Integrator::GaussKonrod {
tolerance,
max_depth,
} => {
let a = Complex::new(a, 0.);
let b = Complex::new(b, 0.);
let integrator = quad_rs::Integrator::default()
.relative_tolerance(*tolerance)
.with_maximum_iter(*max_depth);
integrator
.integrate(Problem(|z| func(z.re)), a..b)
.unwrap()
.result
.result
.unwrap()
}
Integrator::ClenshawCurtis { tolerance } => {
let re =
quadrature::clenshaw_curtis::integrate(|z| func(z).into().re, a, b, *tolerance).integral;
let im =
quadrature::clenshaw_curtis::integrate(|z| func(z).into().im, a, b, *tolerance).integral;
Complex::new(re, im)
}
Integrator::GaussLegendre { degree } => {
let quad = gauss_quad::GaussLegendre::new(*degree.max(&2)).unwrap();
let re = quad.integrate(a, b, |z| func(z).into().re);
let im = quad.integrate(a, b, |z| func(z).into().im);
Complex::new(re, im)
}
}
}
pub fn integrate2d<F, Y>(&self, func: F, a: f64, b: f64, c: f64, d: f64) -> Complex<f64>
where
F: Send + Sync + Fn(f64, f64) -> Y,
Y: Into<Complex<f64>>,
{
match self {
Integrator::Simpson { divs } => simpson2d(func, a, b, c, d, *divs),
Integrator::AdaptiveSimpson {
tolerance,
max_depth,
} => simpson_adaptive_2d(&func, a, b, c, d, *tolerance, *max_depth),
Integrator::GaussKonrod {
tolerance,
max_depth,
} => {
let a = Complex::new(a, 0.);
let b = Complex::new(b, 0.);
let c = Complex::new(c, 0.);
let d = Complex::new(d, 0.);
let integrator = quad_rs::Integrator::default()
.relative_tolerance(*tolerance)
.with_maximum_iter(*max_depth);
integrator
.integrate(
Problem(|z: Complex<f64>| {
quad_rs::Integrator::default()
.relative_tolerance(*tolerance)
.with_maximum_iter(*max_depth)
.integrate(Problem(|w: Complex<f64>| func(z.re, w.re)), c..d)
.unwrap()
.result
.result
.unwrap()
}),
a..b,
)
.unwrap()
.result
.result
.unwrap()
}
Integrator::ClenshawCurtis { tolerance } => {
let re = quadrature::clenshaw_curtis::integrate(
|z| {
quadrature::clenshaw_curtis::integrate(|w| func(z, w).into().re, c, d, *tolerance)
.integral
},
a,
b,
*tolerance,
)
.integral;
let im = quadrature::clenshaw_curtis::integrate(
|z| {
quadrature::clenshaw_curtis::integrate(|w| func(z, w).into().im, c, d, *tolerance)
.integral
},
a,
b,
*tolerance,
)
.integral;
Complex::new(re, im)
}
Integrator::GaussLegendre { degree } => {
let quad = gauss_quad::GaussLegendre::new(*degree.max(&2)).unwrap();
let re = quad.integrate(a, b, |z| quad.integrate(c, d, |w| func(z, w).into().re));
let im = quad.integrate(a, b, |z| quad.integrate(c, d, |w| func(z, w).into().im));
Complex::new(re, im)
}
}
}
}
fn get_simpson_weight(n: usize, divs: usize) -> f64 {
if n == 0 || n == divs {
1.
} else if n.is_odd() {
4.
} else {
2.
}
}
pub fn simpson<F, Y>(func: F, a: f64, b: f64, divs: usize) -> Complex<f64>
where
F: Send + Sync + Fn(f64) -> Y,
Y: Into<Complex<f64>>,
{
let divs = divs + divs % 2 - 2; assert!(divs >= 4, "Steps too low");
let dx = (b - a) / (divs as f64);
let intg = |(i, a_n)| {
let x = a + (i as f64) * dx;
func(x).into() * a_n
};
let result: Complex<f64> = if divs < 128 {
(0..=divs)
.map(|n| (n, get_simpson_weight(n, divs)))
.map(intg)
.sum()
} else {
(0..=divs)
.into_par_iter()
.map(|n| (n, get_simpson_weight(n, divs)))
.map(intg)
.sum()
};
result * (dx / 3.)
}
pub fn simpson2d<F, Y>(func: F, ax: f64, bx: f64, ay: f64, by: f64, divs: usize) -> Complex<f64>
where
F: Send + Sync + Fn(f64, f64) -> Y,
Y: Into<Complex<f64>>,
{
assert!(divs.is_even());
assert!(divs >= 4);
let steps = divs + 1;
let dx = (bx - ax) / (divs as f64);
let dy = (by - ay) / (divs as f64);
let result: Complex<f64> = Steps(ay, by, steps)
.into_par_iter()
.enumerate()
.map(|(ny, y)| {
let sy: Complex<f64> = Steps(ax, bx, steps)
.into_par_iter()
.enumerate()
.map(|(nx, x)| {
let a_n = get_simpson_weight(nx, divs);
func(x, y).into() * a_n
})
.sum();
let a_n = get_simpson_weight(ny, divs);
sy * a_n
})
.sum();
result * (dx * dy / 9.)
}
fn quad_simpsons_mem<F, Y>(
f: F,
a: f64,
fa: Complex<f64>,
b: f64,
fb: Complex<f64>,
) -> (f64, Complex<f64>, Complex<f64>)
where
F: Fn(f64) -> Y,
Y: Into<Complex<f64>>,
{
let m = (a + b) / 2.0;
let fm = f(m).into();
let simpson_value = (b - a).abs() / 6.0 * (fa + 4.0 * fm + fb);
(m, fm, simpson_value)
}
#[allow(clippy::too_many_arguments)]
fn quad_asr<F, Y>(
f: &F,
a: f64,
fa: Complex<f64>,
b: f64,
fb: Complex<f64>,
eps: f64,
whole: Complex<f64>,
m: f64,
fm: Complex<f64>,
max_depth: usize,
) -> Complex<f64>
where
F: Fn(f64) -> Y,
Y: Into<Complex<f64>>,
{
if max_depth == 0 || (eps / 2.0) == eps || (b - a).abs() < f64::EPSILON {
return whole;
}
let (lm, flm, left) = quad_simpsons_mem(f, a, fa, m, fm);
let (rm, frm, right) = quad_simpsons_mem(f, m, fm, b, fb);
let delta = left + right - whole;
if delta.norm() <= 15.0 * eps {
left + right + delta / 15.0
} else {
quad_asr(f, a, fa, m, fm, eps / 2.0, left, lm, flm, max_depth - 1)
+ quad_asr(f, m, fm, b, fb, eps / 2.0, right, rm, frm, max_depth - 1)
}
}
pub fn simpson_adaptive<F, Y>(f: &F, a: f64, b: f64, eps: f64, max_depth: usize) -> Complex<f64>
where
F: Fn(f64) -> Y,
Y: Into<Complex<f64>>,
{
let fa = f(a).into();
let fb = f(b).into();
let (m, fm, whole) = quad_simpsons_mem(f, a, fa, b, fb);
quad_asr(f, a, fa, b, fb, eps, whole, m, fm, max_depth)
}
fn simpson_adaptive_2d<F, Y>(
f: &F,
ax: f64,
bx: f64,
ay: f64,
by: f64,
eps: f64,
max_depth: usize,
) -> Complex<f64>
where
F: Fn(f64, f64) -> Y,
Y: Into<Complex<f64>>,
{
let g = |x| simpson_adaptive(&|y| f(x, y), ay, by, eps, max_depth);
simpson_adaptive(&g, ax, bx, eps, max_depth)
}
#[cfg(test)]
mod tests {
use super::*;
extern crate float_cmp;
use float_cmp::*;
use std::f64::consts::PI;
#[test]
fn integrator_test() {
let actual = simpson(|x| x.sin(), 0., PI, 1000).re;
let expected = 2.;
assert!(
approx_eq!(f64, actual, expected, ulps = 2, epsilon = 1e-11),
"actual: {}, expected: {}",
actual,
expected
);
}
#[test]
fn integrator_2d_test() {
let divs = 1000;
let actual = simpson2d(|x, y| x.sin() * y.powi(3), 0., PI, 0., 2., divs).re;
let expected = (2_f64).powi(4) * 2. / 4.;
assert!(
approx_eq!(f64, actual, expected, ulps = 2, epsilon = 1e-11),
"actual: {}, expected: {}",
actual,
expected
);
}
#[test]
fn adaptive_simpsons_rule_test() {
let f = |x: f64| x.sin() * x.cos() * x.powi(2);
let actual = simpson_adaptive(&f, 0., PI, 1e-8, 1000).re;
let expected = simpson(f, 0., PI, 1000).re;
assert!(
approx_eq!(f64, actual, expected, ulps = 2, epsilon = 1e-8),
"actual: {}, expected: {}",
actual,
expected
);
}
}