use crate::errors::QlResult;
use crate::math::comparison::close_enough;
use crate::math::gammafunction::log_gamma;
use crate::require;
use crate::types::{Real, Size};
pub trait GaussianOrthogonalPolynomial {
fn mu_0(&self) -> Real;
fn alpha(&self, i: Size) -> Real;
fn beta(&self, i: Size) -> Real;
fn w(&self, x: Real) -> Real;
fn value(&self, n: Size, x: Real) -> Real {
let mut previous = 1.0;
if n == 0 {
return previous;
}
let mut current = x - self.alpha(0);
for k in 1..n {
let next = (x - self.alpha(k)) * current - self.beta(k) * previous;
previous = current;
current = next;
}
current
}
fn weighted_value(&self, n: Size, x: Real) -> Real {
self.w(x).sqrt() * self.value(n, x)
}
}
#[derive(Clone, Copy, Debug)]
pub struct GaussJacobiPolynomial {
alpha: Real,
beta: Real,
}
impl GaussJacobiPolynomial {
pub fn new(alpha: Real, beta: Real) -> QlResult<Self> {
require!(
(alpha + beta).is_finite() && alpha + beta > -2.0,
"alpha+beta must be bigger than -2"
);
require!(
alpha.is_finite() && alpha > -1.0,
"alpha must be bigger than -1"
);
require!(
beta.is_finite() && beta > -1.0,
"beta must be bigger than -1"
);
Ok(GaussJacobiPolynomial { alpha, beta })
}
pub fn legendre() -> Self {
GaussJacobiPolynomial {
alpha: 0.0,
beta: 0.0,
}
}
pub fn chebyshev() -> Self {
GaussJacobiPolynomial {
alpha: -0.5,
beta: -0.5,
}
}
pub fn chebyshev2nd() -> Self {
GaussJacobiPolynomial {
alpha: 0.5,
beta: 0.5,
}
}
pub fn gegenbauer(lambda: Real) -> QlResult<Self> {
Self::new(lambda - 0.5, lambda - 0.5)
}
}
impl GaussianOrthogonalPolynomial for GaussJacobiPolynomial {
fn mu_0(&self) -> Real {
let log = log_gamma(self.alpha + 1.0).expect("alpha + 1 > 0 by construction")
+ log_gamma(self.beta + 1.0).expect("beta + 1 > 0 by construction")
- log_gamma(self.alpha + self.beta + 2.0)
.expect("alpha + beta + 2 > 0 by construction");
(2.0 as Real).powf(self.alpha + self.beta + 1.0) * log.exp()
}
fn alpha(&self, i: Size) -> Real {
let i = i as Real;
let mut num = self.beta * self.beta - self.alpha * self.alpha;
let mut denom =
(2.0 * i + self.alpha + self.beta) * (2.0 * i + self.alpha + self.beta + 2.0);
if close_enough(denom, 0.0) {
assert!(
close_enough(num, 0.0),
"can't compute a_k for jacobi integration"
);
num = 2.0 * self.beta;
denom = 2.0 * (2.0 * i + self.alpha + self.beta + 1.0);
assert!(
!close_enough(denom, 0.0),
"can't compute a_k for jacobi integration"
);
}
num / denom
}
fn beta(&self, i: Size) -> Real {
let i = i as Real;
let s = 2.0 * i + self.alpha + self.beta;
let mut num = 4.0 * i * (i + self.alpha) * (i + self.beta) * (i + self.alpha + self.beta);
let mut denom = s * s * (s * s - 1.0);
if close_enough(denom, 0.0) {
assert!(
close_enough(num, 0.0),
"can't compute b_k for jacobi integration"
);
num = 4.0 * i * (i + self.beta) * (2.0 * i + 2.0 * self.alpha + self.beta);
denom = 2.0 * s;
denom *= denom - 1.0;
assert!(
!close_enough(denom, 0.0),
"can't compute b_k for jacobi integration"
);
}
num / denom
}
fn w(&self, x: Real) -> Real {
(1.0 - x).powf(self.alpha) * (1.0 + x).powf(self.beta)
}
}
#[derive(Clone, Copy, Debug)]
pub struct GaussLaguerrePolynomial {
s: Real,
}
impl GaussLaguerrePolynomial {
pub fn new(s: Real) -> QlResult<Self> {
require!(s.is_finite() && s > -1.0, "s must be bigger than -1");
Ok(GaussLaguerrePolynomial { s })
}
}
impl GaussianOrthogonalPolynomial for GaussLaguerrePolynomial {
fn mu_0(&self) -> Real {
log_gamma(self.s + 1.0)
.expect("s + 1 > 0 by construction")
.exp()
}
fn alpha(&self, i: Size) -> Real {
2.0 * i as Real + 1.0 + self.s
}
fn beta(&self, i: Size) -> Real {
let i = i as Real;
i * (i + self.s)
}
fn w(&self, x: Real) -> Real {
x.powf(self.s) * (-x).exp()
}
}
#[derive(Clone, Copy, Debug)]
pub struct GaussHermitePolynomial {
mu: Real,
}
impl GaussHermitePolynomial {
pub fn new(mu: Real) -> QlResult<Self> {
require!(mu.is_finite() && mu > -0.5, "mu must be bigger than -0.5");
Ok(GaussHermitePolynomial { mu })
}
}
impl GaussianOrthogonalPolynomial for GaussHermitePolynomial {
fn mu_0(&self) -> Real {
log_gamma(self.mu + 0.5)
.expect("mu + 1/2 > 0 by construction")
.exp()
}
fn alpha(&self, _i: Size) -> Real {
0.0
}
fn beta(&self, i: Size) -> Real {
if i.is_multiple_of(2) {
i as Real / 2.0
} else {
i as Real / 2.0 + self.mu
}
}
fn w(&self, x: Real) -> Real {
x.abs().powf(2.0 * self.mu) * (-x * x).exp()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct GaussHyperbolicPolynomial;
impl GaussianOrthogonalPolynomial for GaussHyperbolicPolynomial {
fn mu_0(&self) -> Real {
std::f64::consts::PI
}
fn alpha(&self, _i: Size) -> Real {
0.0
}
fn beta(&self, i: Size) -> Real {
use std::f64::consts::{FRAC_PI_2, PI};
if i != 0 {
FRAC_PI_2 * FRAC_PI_2 * i as Real * i as Real
} else {
PI
}
}
fn w(&self, x: Real) -> Real {
1.0 / x.cosh()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MonicLegendre;
impl GaussianOrthogonalPolynomial for MonicLegendre {
fn mu_0(&self) -> Real {
2.0
}
fn alpha(&self, _i: Size) -> Real {
0.0
}
fn beta(&self, i: Size) -> Real {
let k = i as Real;
k * k / (4.0 * k * k - 1.0)
}
fn w(&self, _x: Real) -> Real {
1.0
}
}
#[test]
fn recurrence_reproduces_monic_legendre_polynomials() {
let p = MonicLegendre;
for &x in &[-0.9, -0.3, 0.0, 0.5, 1.0] {
let x: Real = x;
assert!((p.value(0, x) - 1.0).abs() < 1e-15);
assert!((p.value(1, x) - x).abs() < 1e-15);
assert!((p.value(2, x) - (x * x - 1.0 / 3.0)).abs() < 1e-15);
assert!((p.value(3, x) - (x * x * x - 0.6 * x)).abs() < 1e-15);
}
}
#[test]
fn weighted_value_scales_by_sqrt_of_weight() {
let p = MonicLegendre;
assert!((p.weighted_value(2, 0.5) - p.value(2, 0.5)).abs() < 1e-15);
}
#[test]
fn jacobi_with_zero_exponents_matches_legendre_recurrence() {
let jacobi = GaussJacobiPolynomial::legendre();
let legendre = MonicLegendre;
assert!((jacobi.mu_0() - 2.0).abs() < 1e-14);
for i in 0..10 {
assert!(jacobi.alpha(i).abs() < 1e-14);
if i > 0 {
assert!((jacobi.beta(i) - legendre.beta(i)).abs() < 1e-14);
}
}
}
#[test]
fn jacobi_rejects_out_of_domain_exponents() {
assert!(GaussJacobiPolynomial::new(-1.0, 0.0).is_err());
assert!(GaussJacobiPolynomial::new(0.0, -1.0).is_err());
assert!(GaussJacobiPolynomial::new(-0.999, -1.5).is_err());
assert!(GaussJacobiPolynomial::gegenbauer(-0.5).is_err());
}
#[test]
fn laguerre_and_hermite_reject_out_of_domain_exponents() {
assert!(GaussLaguerrePolynomial::new(-1.0).is_err());
assert!(GaussHermitePolynomial::new(-0.5).is_err());
}
}