use std::fmt;
use num::{Float, ToPrimitive};
use crate::utils::checkers::check_gauss_rule_with_limits;
#[derive(Debug, Clone)]
pub struct GaussKronrodError;
impl fmt::Display for GaussKronrodError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Gauss-Kronrod: abscissa Newton iteration did not converge within 50 steps"
)
}
}
type KronrodData = (Vec<f64>, Vec<f64>, Vec<f64>);
struct NodeCtx<'a> {
n: usize,
m: usize,
eps: f64,
coef2: f64,
even: bool,
b: &'a [f64],
}
fn abwe1(ctx: &NodeCtx, mut x: f64) -> Result<(f64, f64), GaussKronrodError> {
let &NodeCtx {
n,
m,
eps,
coef2,
even,
b,
} = ctx;
let mut ka: i32 = if x == 0.0 { 1 } else { 0 };
let mut delta;
let mut fd = 0.0_f64;
for _ in 0..50 {
let mut b0 = 0.0_f64;
let mut b1 = 0.0_f64;
let mut b2 = b[m];
let yy = 4.0 * x * x - 2.0;
let mut d1 = 0.0_f64;
let (mut ai, mut d2, dif) = if even {
let ai0 = (2 * m + 1) as f64;
(ai0, ai0 * b[m], 2.0_f64)
} else {
((m + 1) as f64, 0.0_f64, 1.0_f64)
};
for k in 1..=m {
ai -= dif;
let i = m - k + 1; b0 = b1;
b1 = b2;
let d0 = d1;
d1 = d2;
b2 = yy * b1 - b0 + b[i - 1];
let bi_d = if even { b[i - 1] } else { b[i] };
d2 = yy * d1 - d0 + ai * bi_d;
}
let f = if even {
fd = d2 + d1;
x * (b2 - b1)
} else {
fd = 4.0 * x * d2;
0.5 * (b2 - b0)
};
delta = f / fd;
x -= delta;
if ka == 1 {
break;
}
if delta.abs() <= eps {
ka = 1;
}
}
if ka != 1 {
return Err(GaussKronrodError);
}
let mut d0 = 1.0_f64;
let mut d1 = x;
let mut ai = 0.0_f64;
let mut d2 = 1.0_f64;
for _ in 2..=n {
ai += 1.0;
d2 = ((ai + ai + 1.0) * x * d1 - ai * d0) / (ai + 1.0);
d0 = d1;
d1 = d2;
}
Ok((x, coef2 / (fd * d2)))
}
fn abwe2(ctx: &NodeCtx, mut x: f64) -> Result<(f64, f64, f64), GaussKronrodError> {
let &NodeCtx {
n,
m,
eps,
coef2,
even,
b,
} = ctx;
let mut ka: i32 = if x == 0.0 { 1 } else { 0 };
let mut delta;
let mut pd2 = 0.0_f64; let mut p0_leg = 1.0_f64;
for _ in 0..50 {
let mut p0 = 1.0_f64; let mut p1 = x; let mut pd0 = 0.0_f64;
let mut pd1 = 1.0_f64;
let mut p2 = 0.0_f64;
if n <= 1 {
if f64::EPSILON < x.abs() {
p2 = (3.0 * x * x - 1.0) / 2.0;
pd2 = 3.0 * x;
} else {
p2 = 3.0 * x;
pd2 = 3.0;
}
}
let mut ai = 0.0_f64;
for _ in 2..=n {
ai += 1.0;
p2 = ((ai + ai + 1.0) * x * p1 - ai * p0) / (ai + 1.0);
pd2 = ((ai + ai + 1.0) * (p1 + x * pd1) - ai * pd0) / (ai + 1.0);
p0 = p1;
p1 = p2;
pd0 = pd1;
pd1 = pd2;
}
p0_leg = p0;
delta = p2 / pd2;
x -= delta;
if ka == 1 {
break;
}
if delta.abs() <= eps {
ka = 1;
}
}
if ka != 1 {
return Err(GaussKronrodError);
}
let w_g = 2.0 / (n as f64 * pd2 * p0_leg);
let mut p0 = 0.0_f64;
let mut p1 = 0.0_f64;
let mut p2 = b[m];
let yy = 4.0 * x * x - 2.0;
for k in 1..=m {
let i = m - k + 1; p0 = p1;
p1 = p2;
p2 = yy * p1 - p0 + b[i - 1];
}
let w_gk = if even {
w_g + coef2 / (pd2 * x * (p2 - p1))
} else {
w_g + 2.0 * coef2 / (pd2 * (p2 - p0))
};
Ok((x, w_gk, w_g))
}
fn kronrod_nodes(n: usize, eps: f64) -> Result<KronrodData, GaussKronrodError> {
let m = n.div_ceil(2);
let even = 2 * m == n;
let mut x = vec![0.0_f64; n + 1];
let mut w_gk = vec![0.0_f64; n + 1];
let mut w_g = vec![0.0_f64; n + 1];
let mut b = vec![0.0_f64; m + 1];
let mut tau = vec![0.0_f64; m.max(1)];
let an = n as f64;
if m > 0 {
tau[0] = (an + 2.0) / (an + an + 3.0);
b[m - 1] = tau[0] - 1.0;
let mut ak = an;
for l in 1..m {
ak += 2.0;
tau[l] = ((ak - 1.0) * ak - an * (an + 1.0)) * (ak + 2.0) * tau[l - 1]
/ (ak * ((ak + 3.0) * (ak + 2.0) - an * (an + 1.0)));
b[m - l - 1] = tau[l];
for ll in 1..=l {
b[m - l - 1] += tau[ll - 1] * b[m - l + ll - 1];
}
}
}
b[m] = 1.0;
let mut coef2 = 2.0 / (2 * n + 1) as f64;
for i in 1..=n {
coef2 *= 4.0 * i as f64 / (n + i) as f64;
}
let theta0 = std::f64::consts::PI / (2.0 * an + 1.0);
let bb_init = theta0.sin();
let x1_init = theta0.cos();
let s = bb_init; let c = x1_init; let coef = 1.0 - (1.0 - 1.0 / an) / (8.0 * an * an);
let ctx = NodeCtx {
n,
m,
eps,
coef2,
even,
b: &b,
};
let mut xx = coef * x1_init;
let mut x1 = x1_init;
let mut bb = bb_init;
let mut k = 1_usize;
while k <= n {
let (xx_new, wk) = abwe1(&ctx, xx)?;
xx = xx_new;
w_gk[k - 1] = wk;
w_g[k - 1] = 0.0;
x[k - 1] = xx;
let y = x1;
x1 = y * c - bb * s;
bb = y * s + bb * c;
xx = if k == n { 0.0 } else { coef * x1 };
let (xx_new, wgk, wg) = abwe2(&ctx, xx)?;
xx = xx_new;
w_gk[k] = wgk;
w_g[k] = wg;
x[k] = xx;
let y = x1;
x1 = y * c - bb * s;
bb = y * s + bb * c;
xx = coef * x1;
k += 2;
}
if even {
let (xx_zero, wk) = abwe1(&ctx, 0.0)?;
w_gk[n] = wk;
w_g[n] = 0.0;
x[n] = xx_zero;
}
Ok((x, w_gk, w_g))
}
pub fn gauss_kronrod_rule<Func, F1, F2>(
func: Func,
lower_limit: F1,
upper_limit: F1,
n: usize,
) -> Result<(f64, f64), GaussKronrodError>
where
F1: Float + ToPrimitive,
F2: Float + ToPrimitive,
Func: Fn(F1) -> F2,
{
check_gauss_rule_with_limits(lower_limit, upper_limit, n);
let a = lower_limit.to_f64().unwrap();
let b = upper_limit.to_f64().unwrap();
let (x, w_gk, w_g) = kronrod_nodes(n, f64::EPSILON)?;
let mid = (a + b) / 2.0;
let half_len = (b - a) / 2.0;
let f = |t: f64| -> f64 {
func(F1::from(t).unwrap())
.to_f64()
.expect("failed to convert f(x) to f64")
};
let mut gk_sum = w_gk[n] * f(mid);
let mut g_sum = w_g[n] * f(mid);
for i in 0..n {
let xi = x[i]; let fp = f(mid + half_len * xi);
let fm = f(mid - half_len * xi);
gk_sum += w_gk[i] * (fp + fm);
g_sum += w_g[i] * (fp + fm);
}
let integral = half_len * gk_sum;
let error = (half_len * (gk_sum - g_sum)).abs();
Ok((integral, error))
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-10;
#[test]
fn test_exp() {
let (integral, error) = gauss_kronrod_rule(|x: f64| x.exp(), 0.0, 1.0, 7).unwrap();
let exact = std::f64::consts::E - 1.0;
assert!(
(integral - exact).abs() < EPSILON,
"integral={integral}, exact={exact}"
);
assert!(error < EPSILON);
}
#[test]
fn test_polynomial() {
let (integral, _) = gauss_kronrod_rule(|x: f64| x * x, 0.0, 1.0, 7).unwrap();
assert!((integral - 1.0 / 3.0).abs() < EPSILON);
}
#[test]
fn test_sin() {
let (integral, error) =
gauss_kronrod_rule(|x: f64| x.sin(), 0.0, std::f64::consts::PI, 7).unwrap();
assert!((integral - 2.0).abs() < EPSILON, "integral={integral}");
assert!(error < EPSILON);
}
#[test]
fn test_symmetric_interval() {
let (integral, _) = gauss_kronrod_rule(|x: f64| x.powi(3), -1.0, 1.0, 7).unwrap();
assert!(integral.abs() < EPSILON);
}
#[test]
fn test_f32_input() {
let (integral, _) =
gauss_kronrod_rule(|x: f32| (x * x) as f64, 0.0_f32, 1.0_f32, 7).unwrap();
assert!((integral - 1.0 / 3.0).abs() < 1e-6);
}
#[test]
fn test_even_n() {
let (integral, _) = gauss_kronrod_rule(|x: f64| x.exp(), 0.0, 1.0, 4).unwrap();
let exact = std::f64::consts::E - 1.0;
assert!((integral - exact).abs() < EPSILON);
}
#[test]
#[should_panic(expected = "number of steps can't be zero")]
fn test_n_zero() {
let _ = gauss_kronrod_rule(|x: f64| x, 0.0, 1.0, 0);
}
#[test]
#[should_panic(expected = "integration limits a and b can't be infinite")]
fn test_infinite_limit() {
let _ = gauss_kronrod_rule(|x: f64| x, f64::NEG_INFINITY, 1.0, 7);
}
#[test]
#[should_panic(expected = "lower_limit must not exceed upper_limit")]
fn test_inverted_limits() {
let _ = gauss_kronrod_rule(|x: f64| x, 1.0, 0.0, 7);
}
}