use crate::error::QuadratureError;
use crate::gauss_chebyshev::{GaussChebyshevFirstKind, GaussChebyshevSecondKind};
use crate::gauss_hermite::GaussHermite;
use crate::gauss_jacobi::GaussJacobi;
use crate::gauss_laguerre::GaussLaguerre;
#[cfg(not(feature = "std"))]
use num_traits::Float as _;
#[derive(Debug, Clone)]
pub enum WeightFunction {
Jacobi { alpha: f64, beta: f64 },
Laguerre { alpha: f64 },
Hermite,
ChebyshevI,
ChebyshevII,
LogWeight,
}
#[derive(Debug, Clone)]
pub struct WeightedIntegrator {
weight: WeightFunction,
order: usize,
}
impl WeightedIntegrator {
pub fn new(weight: WeightFunction, order: usize) -> Result<Self, QuadratureError> {
if order == 0 {
return Err(QuadratureError::ZeroOrder);
}
Ok(Self { weight, order })
}
#[must_use]
pub fn with_order(mut self, n: usize) -> Self {
self.order = n;
self
}
pub fn integrate<G>(&self, f: G) -> f64
where
G: Fn(f64) -> f64,
{
match &self.weight {
WeightFunction::Jacobi { alpha, beta } => {
let gj = GaussJacobi::new(self.order, *alpha, *beta).unwrap();
let rule = gj.rule();
let mut sum = 0.0;
for (node, weight) in rule.nodes.iter().zip(rule.weights.iter()) {
sum += weight * f(*node);
}
sum
}
WeightFunction::Laguerre { alpha } => {
let gl = GaussLaguerre::new(self.order, *alpha).unwrap();
let rule = gl.rule();
let mut sum = 0.0;
for (node, weight) in rule.nodes.iter().zip(rule.weights.iter()) {
sum += weight * f(*node);
}
sum
}
WeightFunction::Hermite => {
let gh = GaussHermite::new(self.order).unwrap();
let rule = gh.rule();
let mut sum = 0.0;
for (node, weight) in rule.nodes.iter().zip(rule.weights.iter()) {
sum += weight * f(*node);
}
sum
}
WeightFunction::ChebyshevI => {
let gc = GaussChebyshevFirstKind::new(self.order).unwrap();
let rule = gc.rule();
let mut sum = 0.0;
for (node, weight) in rule.nodes.iter().zip(rule.weights.iter()) {
sum += weight * f(*node);
}
sum
}
WeightFunction::ChebyshevII => {
let gc = GaussChebyshevSecondKind::new(self.order).unwrap();
let rule = gc.rule();
let mut sum = 0.0;
for (node, weight) in rule.nodes.iter().zip(rule.weights.iter()) {
sum += weight * f(*node);
}
sum
}
WeightFunction::LogWeight => {
let gl = GaussLaguerre::new(self.order, 1.0).unwrap();
let rule = gl.rule();
let mut sum = 0.0;
for (node, weight) in rule.nodes.iter().zip(rule.weights.iter()) {
sum += weight * f((-*node).exp());
}
sum
}
}
}
pub fn integrate_over<G>(&self, a: f64, b: f64, f: G) -> f64
where
G: Fn(f64) -> f64,
{
match &self.weight {
WeightFunction::Jacobi { alpha, beta } => {
let half = 0.5 * (b - a);
let mid = 0.5 * (a + b);
self.integrate(|t| f(half * t + mid)) * half.powf(alpha + beta + 1.0)
}
WeightFunction::ChebyshevI => {
let half = 0.5 * (b - a);
let mid = 0.5 * (a + b);
self.integrate(|t| f(half * t + mid))
}
WeightFunction::ChebyshevII => {
let half = 0.5 * (b - a);
let mid = 0.5 * (a + b);
self.integrate(|t| f(half * t + mid)) * half * half
}
WeightFunction::LogWeight => {
let width = b - a;
let inner = self.integrate(|u| f(a + width * u));
width * inner
}
WeightFunction::Laguerre { .. } | WeightFunction::Hermite => self.integrate(f),
}
}
}
pub fn weighted_integrate<G>(
f: G,
weight: WeightFunction,
order: usize,
) -> Result<f64, QuadratureError>
where
G: Fn(f64) -> f64,
{
let wi = WeightedIntegrator::new(weight, order)?;
Ok(wi.integrate(f))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn jacobi_weight_sum() {
let result = weighted_integrate(
|_| 1.0,
WeightFunction::Jacobi {
alpha: 0.5,
beta: 0.5,
},
20,
)
.unwrap();
assert!(
(result - core::f64::consts::FRAC_PI_2).abs() < 1e-10,
"result={result}"
);
}
#[test]
fn laguerre_constant() {
let result =
weighted_integrate(|_| 1.0, WeightFunction::Laguerre { alpha: 0.0 }, 10).unwrap();
assert!((result - 1.0).abs() < 1e-10, "result={result}");
}
#[test]
fn laguerre_linear() {
let result =
weighted_integrate(|x| x, WeightFunction::Laguerre { alpha: 0.0 }, 10).unwrap();
assert!((result - 1.0).abs() < 1e-10, "result={result}");
}
#[test]
fn hermite_constant() {
let result = weighted_integrate(|_| 1.0, WeightFunction::Hermite, 10).unwrap();
assert!(
(result - core::f64::consts::PI.sqrt()).abs() < 1e-10,
"result={result}"
);
}
#[test]
fn chebyshev_i_constant() {
let result = weighted_integrate(|_| 1.0, WeightFunction::ChebyshevI, 20).unwrap();
assert!(
(result - core::f64::consts::PI).abs() < 1e-12,
"result={result}"
);
}
#[test]
fn chebyshev_ii_constant() {
let result = weighted_integrate(|_| 1.0, WeightFunction::ChebyshevII, 20).unwrap();
assert!(
(result - core::f64::consts::FRAC_PI_2).abs() < 1e-12,
"result={result}"
);
}
#[test]
fn log_weight_constant() {
let result = weighted_integrate(|_| 1.0, WeightFunction::LogWeight, 20).unwrap();
assert!((result - 1.0).abs() < 1e-10, "result={result}");
}
#[test]
fn log_weight_linear() {
let result = weighted_integrate(|x| x, WeightFunction::LogWeight, 20).unwrap();
assert!((result - 0.25).abs() < 1e-10, "result={result}");
}
#[test]
fn log_weight_quadratic() {
let result = weighted_integrate(|x| x * x, WeightFunction::LogWeight, 20).unwrap();
assert!(
(result - 1.0 / 9.0).abs() < 1e-10,
"result={result}, expected={}",
1.0 / 9.0
);
}
#[test]
fn zero_order() {
assert!(weighted_integrate(|_| 1.0, WeightFunction::Hermite, 0).is_err());
}
#[test]
fn integrate_over_jacobi_legendre_on_0_2() {
let wi = WeightedIntegrator::new(
WeightFunction::Jacobi {
alpha: 0.0,
beta: 0.0,
},
20,
)
.unwrap();
let result = wi.integrate_over(0.0, 2.0, |x| x * x);
assert!(
(result - 8.0 / 3.0).abs() < 1e-10,
"result={result}, expected={}",
8.0 / 3.0
);
}
#[test]
fn integrate_over_jacobi_legendre_on_1_3() {
let wi = WeightedIntegrator::new(
WeightFunction::Jacobi {
alpha: 0.0,
beta: 0.0,
},
10,
)
.unwrap();
let result = wi.integrate_over(1.0, 3.0, |x| x);
assert!((result - 4.0).abs() < 1e-10, "result={result}");
}
#[test]
fn integrate_over_chebyshev_i_on_0_2() {
let wi = WeightedIntegrator::new(WeightFunction::ChebyshevI, 20).unwrap();
let result = wi.integrate_over(0.0, 2.0, |_| 1.0);
assert!(
(result - core::f64::consts::PI).abs() < 1e-10,
"result={result}"
);
}
#[test]
fn integrate_over_chebyshev_ii_on_0_2() {
let wi = WeightedIntegrator::new(WeightFunction::ChebyshevII, 20).unwrap();
let result = wi.integrate_over(0.0, 2.0, |_| 1.0);
assert!(
(result - core::f64::consts::FRAC_PI_2).abs() < 1e-10,
"result={result}"
);
}
#[test]
fn integrate_over_laguerre_ignores_bounds() {
let wi = WeightedIntegrator::new(WeightFunction::Laguerre { alpha: 0.0 }, 10).unwrap();
let natural = wi.integrate(|_| 1.0);
let remapped = wi.integrate_over(5.0, 10.0, |_| 1.0);
assert!(
(natural - remapped).abs() < 1e-14,
"natural={natural}, remapped={remapped}"
);
}
#[test]
fn integrate_over_hermite_ignores_bounds() {
let wi = WeightedIntegrator::new(WeightFunction::Hermite, 10).unwrap();
let natural = wi.integrate(|_| 1.0);
let remapped = wi.integrate_over(5.0, 10.0, |_| 1.0);
assert!(
(natural - remapped).abs() < 1e-14,
"natural={natural}, remapped={remapped}"
);
}
#[test]
fn integrate_over_jacobi_on_0_4() {
let wi = WeightedIntegrator::new(
WeightFunction::Jacobi {
alpha: 0.5,
beta: 0.5,
},
30,
)
.unwrap();
let result = wi.integrate_over(0.0, 4.0, |_| 1.0);
assert!(
(result - 2.0 * core::f64::consts::PI).abs() < 1e-6,
"result={result}, expected={}",
2.0 * core::f64::consts::PI
);
}
#[test]
fn integrate_over_chebyshev_i_on_0_4() {
let wi = WeightedIntegrator::new(WeightFunction::ChebyshevI, 20).unwrap();
let result = wi.integrate_over(0.0, 4.0, |_| 1.0);
assert!(
(result - core::f64::consts::PI).abs() < 1e-10,
"result={result}, expected π"
);
}
#[test]
fn integrate_over_chebyshev_ii_on_0_4() {
let wi = WeightedIntegrator::new(WeightFunction::ChebyshevII, 20).unwrap();
let result = wi.integrate_over(0.0, 4.0, |_| 1.0);
assert!(
(result - 2.0 * core::f64::consts::PI).abs() < 1e-6,
"result={result}, expected={}",
2.0 * core::f64::consts::PI
);
}
#[test]
fn integrate_over_log_weight_on_0_2() {
let wi = WeightedIntegrator::new(WeightFunction::LogWeight, 20).unwrap();
let result = wi.integrate_over(0.0, 2.0, |_| 1.0);
assert!((result - 2.0).abs() < 1e-10, "result={result}");
}
}