use crate::error::{SpecialError, SpecialResult};
use crate::orthogonal::{
chebyshev, gegenbauer, hermite, hermite_prob, jacobi, laguerre, laguerre_generalized, legendre,
};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
#[inline(always)]
fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
F::from(value).unwrap_or_else(|| {
if value > 0.0 {
F::infinity()
} else if value < 0.0 {
F::neg_infinity()
} else {
F::zero()
}
})
}
pub fn legendre_roots(n: usize) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
let diag = vec![0.0f64; n];
let mut sub_diag = vec![0.0f64; n.saturating_sub(1)];
for i in 0..n.saturating_sub(1) {
let ip1 = (i + 1) as f64;
sub_diag[i] = ip1 / (4.0 * ip1 * ip1 - 1.0).sqrt();
}
let eigenvalues = symmetric_tridiag_eigenvalues(&diag, &sub_diag)?;
let mut roots = eigenvalues;
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn chebyshev_t_roots(n: usize) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
let mut roots: Vec<f64> = (1..=n)
.map(|k| {
let angle = (2 * k - 1) as f64 * std::f64::consts::PI / (2.0 * n as f64);
angle.cos()
})
.collect();
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn chebyshev_u_roots(n: usize) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
let mut roots: Vec<f64> = (1..=n)
.map(|k| {
let angle = k as f64 * std::f64::consts::PI / (n as f64 + 1.0);
angle.cos()
})
.collect();
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn hermite_roots(n: usize) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
let diag = vec![0.0f64; n];
let mut sub_diag = vec![0.0f64; n.saturating_sub(1)];
for i in 0..n.saturating_sub(1) {
sub_diag[i] = ((i + 1) as f64 / 2.0).sqrt();
}
let eigenvalues = symmetric_tridiag_eigenvalues(&diag, &sub_diag)?;
let mut roots = eigenvalues;
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn hermite_prob_roots(n: usize) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
let diag = vec![0.0f64; n];
let mut sub_diag = vec![0.0f64; n.saturating_sub(1)];
for i in 0..n.saturating_sub(1) {
sub_diag[i] = ((i + 1) as f64).sqrt();
}
let eigenvalues = symmetric_tridiag_eigenvalues(&diag, &sub_diag)?;
let mut roots = eigenvalues;
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn laguerre_roots(n: usize) -> SpecialResult<Vec<f64>> {
laguerre_generalized_roots(n, 0.0)
}
pub fn laguerre_generalized_roots(n: usize, alpha: f64) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
if alpha <= -1.0 {
return Err(SpecialError::DomainError(
"alpha must be > -1 for generalized Laguerre roots".to_string(),
));
}
let mut diag = vec![0.0f64; n];
let mut sub_diag = vec![0.0f64; n.saturating_sub(1)];
for i in 0..n {
diag[i] = 2.0 * (i as f64) + alpha + 1.0;
}
for i in 0..n.saturating_sub(1) {
let ip1 = (i + 1) as f64;
sub_diag[i] = (ip1 * (ip1 + alpha)).sqrt();
}
let eigenvalues = symmetric_tridiag_eigenvalues(&diag, &sub_diag)?;
let mut roots = eigenvalues;
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn jacobi_roots(n: usize, alpha: f64, beta: f64) -> SpecialResult<Vec<f64>> {
if n == 0 {
return Err(SpecialError::ValueError(
"Degree must be >= 1 for root computation".to_string(),
));
}
if alpha <= -1.0 || beta <= -1.0 {
return Err(SpecialError::DomainError(
"alpha and beta must be > -1 for Jacobi roots".to_string(),
));
}
let mut diag = vec![0.0f64; n];
let mut sub_diag = vec![0.0f64; n.saturating_sub(1)];
for i in 0..n {
let i_f = i as f64;
let denom = (2.0 * i_f + alpha + beta) * (2.0 * i_f + alpha + beta + 2.0);
if denom.abs() < 1e-300 {
diag[i] = 0.0;
} else {
diag[i] = (beta * beta - alpha * alpha) / denom;
}
}
for i in 0..n.saturating_sub(1) {
let ip1 = (i + 1) as f64;
let numer = 4.0 * ip1 * (ip1 + alpha) * (ip1 + beta) * (ip1 + alpha + beta);
let denom_base = 2.0 * ip1 + alpha + beta;
let denom = denom_base * denom_base * (denom_base + 1.0) * (denom_base - 1.0);
if denom.abs() < 1e-300 {
sub_diag[i] = 0.0;
} else {
sub_diag[i] = (numer / denom).sqrt();
}
}
let eigenvalues = symmetric_tridiag_eigenvalues(&diag, &sub_diag)?;
let mut roots = eigenvalues;
roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(roots)
}
pub fn gegenbauer_roots(n: usize, lambda: f64) -> SpecialResult<Vec<f64>> {
if lambda <= -0.5 {
return Err(SpecialError::DomainError(
"lambda must be > -1/2 for Gegenbauer roots".to_string(),
));
}
let alpha = lambda - 0.5;
let beta = alpha;
jacobi_roots(n, alpha, beta)
}
pub fn legendre_batch<F: Float + FromPrimitive + Debug>(n: usize, points: &[F]) -> Vec<F> {
points.iter().map(|&x| legendre(n, x)).collect()
}
pub fn chebyshev_t_batch<F: Float + FromPrimitive + Debug>(n: usize, points: &[F]) -> Vec<F> {
points.iter().map(|&x| chebyshev(n, x, true)).collect()
}
pub fn chebyshev_u_batch<F: Float + FromPrimitive + Debug>(n: usize, points: &[F]) -> Vec<F> {
points.iter().map(|&x| chebyshev(n, x, false)).collect()
}
pub fn hermite_batch<F: Float + FromPrimitive + Debug>(n: usize, points: &[F]) -> Vec<F> {
points.iter().map(|&x| hermite(n, x)).collect()
}
pub fn hermite_prob_batch<F: Float + FromPrimitive + Debug>(n: usize, points: &[F]) -> Vec<F> {
points.iter().map(|&x| hermite_prob(n, x)).collect()
}
pub fn laguerre_batch<F: Float + FromPrimitive + Debug>(n: usize, points: &[F]) -> Vec<F> {
points.iter().map(|&x| laguerre(n, x)).collect()
}
pub fn laguerre_generalized_batch<F: Float + FromPrimitive + Debug>(
n: usize,
alpha: F,
points: &[F],
) -> Vec<F> {
points
.iter()
.map(|&x| laguerre_generalized(n, alpha, x))
.collect()
}
pub fn jacobi_batch<F: Float + FromPrimitive + Debug>(
n: usize,
alpha: F,
beta: F,
points: &[F],
) -> Vec<F> {
points.iter().map(|&x| jacobi(n, alpha, beta, x)).collect()
}
pub fn gegenbauer_batch<F: Float + FromPrimitive + Debug>(
n: usize,
lambda: F,
points: &[F],
) -> Vec<F> {
points.iter().map(|&x| gegenbauer(n, lambda, x)).collect()
}
pub fn legendre_coefficients(n: usize) -> Vec<f64> {
polynomial_coefficients_from_recurrence(n, |k| {
let k_f = k as f64;
let a = (2.0 * k_f + 1.0) / (k_f + 1.0); let b = 0.0; let c = k_f / (k_f + 1.0); (a, b, c)
})
}
pub fn chebyshev_t_coefficients(n: usize) -> Vec<f64> {
polynomial_coefficients_from_recurrence(n, |k| {
if k == 0 {
(1.0, 0.0, 0.0)
} else {
(2.0, 0.0, 1.0)
}
})
}
pub fn hermite_coefficients(n: usize) -> Vec<f64> {
polynomial_coefficients_from_recurrence(n, |k| {
let k_f = k as f64;
(2.0, 0.0, 2.0 * k_f)
})
}
pub fn hermite_prob_coefficients(n: usize) -> Vec<f64> {
polynomial_coefficients_from_recurrence(n, |k| {
let k_f = k as f64;
(1.0, 0.0, k_f)
})
}
pub fn legendre_kth_derivative<F: Float + FromPrimitive + Debug>(n: usize, k: usize, x: F) -> F {
if k == 0 {
return legendre(n, x);
}
if k > n {
return F::zero();
}
if k == 1 {
return crate::orthogonal_ext::legendre_derivative(n, x);
}
let h_base = const_f64::<F>(1e-4);
richardson_kth_derivative(|t| legendre(n, t), x, k, h_base)
}
pub fn chebyshev_t_kth_derivative<F: Float + FromPrimitive + Debug>(n: usize, k: usize, x: F) -> F {
if k == 0 {
return chebyshev(n, x, true);
}
if k > n {
return F::zero();
}
if k == 1 {
return crate::orthogonal_ext::chebyshev_t_derivative(n, x);
}
let h_base = const_f64::<F>(1e-4);
richardson_kth_derivative(|t| chebyshev(n, t, true), x, k, h_base)
}
pub fn hermite_kth_derivative<F: Float + FromPrimitive + Debug>(n: usize, k: usize, x: F) -> F {
if k == 0 {
return hermite(n, x);
}
if k > n {
return F::zero();
}
let two_pow_k = const_f64::<F>(2.0_f64.powi(k as i32));
let mut falling_factorial = F::one();
for i in 0..k {
falling_factorial = falling_factorial * F::from(n - i).unwrap_or(F::zero());
}
two_pow_k * falling_factorial * hermite(n - k, x)
}
pub fn hermite_prob_kth_derivative<F: Float + FromPrimitive + Debug>(
n: usize,
k: usize,
x: F,
) -> F {
if k == 0 {
return hermite_prob(n, x);
}
if k > n {
return F::zero();
}
let mut falling_factorial = F::one();
for i in 0..k {
falling_factorial = falling_factorial * F::from(n - i).unwrap_or(F::zero());
}
falling_factorial * hermite_prob(n - k, x)
}
pub fn eval_three_term_recurrence<F: Float + FromPrimitive + Debug>(
n: usize,
x: F,
recurrence_fn: impl Fn(usize) -> (F, F, F),
) -> F {
if n == 0 {
return F::one();
}
let (a0, b0, _c0) = recurrence_fn(0);
let mut p_prev = F::one();
let mut p_curr = a0 * x + b0;
for k in 1..n {
let (a_k, b_k, c_k) = recurrence_fn(k);
let p_next = (a_k * x + b_k) * p_curr - c_k * p_prev;
p_prev = p_curr;
p_curr = p_next;
}
p_curr
}
pub fn eval_all_degrees<F: Float + FromPrimitive + Debug>(
n_max: usize,
x: F,
recurrence_fn: impl Fn(usize) -> (F, F, F),
) -> Vec<F> {
let mut result = Vec::with_capacity(n_max + 1);
result.push(F::one());
if n_max == 0 {
return result;
}
let (a0, b0, _c0) = recurrence_fn(0);
result.push(a0 * x + b0);
for k in 1..n_max {
let (a_k, b_k, c_k) = recurrence_fn(k);
let p_next = (a_k * x + b_k) * result[k] - c_k * result[k - 1];
result.push(p_next);
}
result
}
pub fn legendre_all<F: Float + FromPrimitive + Debug>(n_max: usize, x: F) -> Vec<F> {
eval_all_degrees(n_max, x, |k| {
let k_f = F::from(k).unwrap_or(F::zero());
let a = (k_f + k_f + F::one()) / (k_f + F::one());
let b = F::zero();
let c = k_f / (k_f + F::one());
(a, b, c)
})
}
pub fn chebyshev_t_all<F: Float + FromPrimitive + Debug>(n_max: usize, x: F) -> Vec<F> {
eval_all_degrees(n_max, x, |k| {
let two = F::one() + F::one();
if k == 0 {
(F::one(), F::zero(), F::zero())
} else {
(two, F::zero(), F::one())
}
})
}
pub fn hermite_all<F: Float + FromPrimitive + Debug>(n_max: usize, x: F) -> Vec<F> {
eval_all_degrees(n_max, x, |k| {
let two = F::one() + F::one();
let k_f = F::from(k).unwrap_or(F::zero());
(two, F::zero(), two * k_f)
})
}
pub fn clenshaw_chebyshev<F: Float + FromPrimitive + Debug>(coeffs: &[F], x: F) -> F {
let n = coeffs.len();
if n == 0 {
return F::zero();
}
if n == 1 {
return coeffs[0];
}
let two = F::one() + F::one();
let mut b_kplus2 = F::zero();
let mut b_kplus1 = F::zero();
for k in (1..n).rev() {
let b_k = two * x * b_kplus1 - b_kplus2 + coeffs[k];
b_kplus2 = b_kplus1;
b_kplus1 = b_k;
}
x * b_kplus1 - b_kplus2 + coeffs[0]
}
pub fn clenshaw_legendre<F: Float + FromPrimitive + Debug>(coeffs: &[F], x: F) -> F {
let n = coeffs.len();
if n == 0 {
return F::zero();
}
if n == 1 {
return coeffs[0];
}
let mut b_kplus2 = F::zero();
let mut b_kplus1 = F::zero();
for k in (0..n).rev() {
let k_f = F::from(k).unwrap_or(F::zero());
let alpha = (k_f + k_f + F::one()) / (k_f + F::one()) * x;
let beta = if k + 2 < n {
(k_f + F::one()) / (k_f + F::one() + F::one())
} else {
F::zero()
};
let b_k = alpha * b_kplus1 - beta * b_kplus2 + coeffs[k];
b_kplus2 = b_kplus1;
b_kplus1 = b_k;
}
b_kplus1
}
fn polynomial_coefficients_from_recurrence(
n: usize,
recurrence_fn: impl Fn(usize) -> (f64, f64, f64),
) -> Vec<f64> {
if n == 0 {
return vec![1.0];
}
let mut coeffs: Vec<Vec<f64>> = Vec::with_capacity(n + 1);
coeffs.push(vec![1.0]);
let (a0, b0, _c0) = recurrence_fn(0);
coeffs.push(vec![b0, a0]);
for k in 1..n {
let (a_k, b_k, c_k) = recurrence_fn(k);
let prev = &coeffs[k];
let prev_prev = &coeffs[k - 1];
let mut new_coeffs = vec![0.0; k + 2];
for (i, &c) in prev.iter().enumerate() {
new_coeffs[i + 1] += a_k * c;
}
for (i, &c) in prev.iter().enumerate() {
new_coeffs[i] += b_k * c;
}
for (i, &c) in prev_prev.iter().enumerate() {
new_coeffs[i] -= c_k * c;
}
coeffs.push(new_coeffs);
}
coeffs.into_iter().last().unwrap_or_else(|| vec![1.0])
}
fn richardson_kth_derivative<F: Float + FromPrimitive + Debug>(
f: impl Fn(F) -> F,
x: F,
k: usize,
h_base: F,
) -> F {
let two = F::one() + F::one();
let compute_diff = |h: F| -> F {
let half_k = F::from(k).unwrap_or(F::zero()) / two;
let mut result = F::zero();
let mut binom = 1i64;
for j in 0..=k {
let sign = if j % 2 == 0 { F::one() } else { -F::one() };
let point = x + (half_k - F::from(j).unwrap_or(F::zero())) * h;
result = result + sign * F::from(binom).unwrap_or(F::zero()) * f(point);
if j < k {
binom = binom * (k - j) as i64 / (j + 1) as i64;
}
}
let h_pow_k = h.powi(k as i32);
if h_pow_k.abs() < F::from(1e-300).unwrap_or(F::zero()) {
return F::zero();
}
result / h_pow_k
};
let d1 = compute_diff(h_base);
let d2 = compute_diff(h_base / two);
let four = two * two;
(four * d2 - d1) / (four - F::one())
}
fn symmetric_tridiag_eigenvalues(diag: &[f64], sub_diag: &[f64]) -> SpecialResult<Vec<f64>> {
let n = diag.len();
if n == 0 {
return Ok(vec![]);
}
if n == 1 {
return Ok(vec![diag[0]]);
}
let mut d = diag.to_vec();
let mut e = vec![0.0f64; n];
for i in 0..n - 1 {
e[i] = sub_diag[i];
}
e[n - 1] = 0.0;
for l in 0..n {
let mut iteration = 0u32;
let max_iter = 300u32;
loop {
let mut m = l;
while m < n - 1 {
let dd = d[m].abs() + d[m + 1].abs();
if e[m].abs() <= f64::EPSILON * dd {
break;
}
m += 1;
}
if m == l {
break; }
iteration += 1;
if iteration > max_iter {
return Err(SpecialError::ConvergenceError(format!(
"Tridiagonal eigenvalue computation did not converge after {max_iter} iterations for l={l}"
)));
}
let mut g = (d[l + 1] - d[l]) / (2.0 * e[l]);
let mut r = (g * g + 1.0).sqrt();
g = d[m] - d[l] + e[l] / (g + if g >= 0.0 { r } else { -r });
let mut s = 1.0;
let mut c = 1.0;
let mut p = 0.0;
let mut i = m;
let mut early_break = false;
while i > l {
i -= 1;
let f = s * e[i];
let b = c * e[i];
r = (f * f + g * g).sqrt();
e[i + 1] = r;
if r.abs() < 1e-300 {
d[i + 1] -= p;
e[m] = 0.0;
early_break = true;
break;
}
s = f / r;
c = g / r;
g = d[i + 1] - p;
r = (d[i] - g) * s + 2.0 * c * b;
p = s * r;
d[i + 1] = g + p;
g = c * r - b;
}
if !early_break {
d[l] -= p;
e[l] = g;
e[m] = 0.0;
}
}
}
Ok(d)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_legendre_roots_degree1() {
let roots = legendre_roots(1).expect("failed");
assert_eq!(roots.len(), 1);
assert!(roots[0].abs() < 1e-14);
}
#[test]
fn test_legendre_roots_degree2() {
let roots = legendre_roots(2).expect("failed");
assert_eq!(roots.len(), 2);
let expected = 1.0 / 3.0_f64.sqrt();
assert!((roots[0] + expected).abs() < 1e-12);
assert!((roots[1] - expected).abs() < 1e-12);
}
#[test]
fn test_legendre_roots_degree3() {
let roots = legendre_roots(3).expect("failed");
assert_eq!(roots.len(), 3);
assert!(
roots[1].abs() < 1e-12,
"middle root should be 0, got {}",
roots[1]
);
let s = (3.0 / 5.0_f64).sqrt();
assert!((roots[0] + s).abs() < 1e-12);
assert!((roots[2] - s).abs() < 1e-12);
}
#[test]
fn test_legendre_roots_are_roots() {
let roots = legendre_roots(5).expect("failed");
for &r in &roots {
let val = legendre(5, r);
assert!(val.abs() < 1e-10, "P_5({r}) = {val}, expected ~0");
}
}
#[test]
fn test_chebyshev_t_roots_count() {
let roots = chebyshev_t_roots(5).expect("failed");
assert_eq!(roots.len(), 5);
for &r in &roots {
assert!(r.abs() <= 1.0, "root out of range: {r}");
}
}
#[test]
fn test_chebyshev_t_roots_are_roots() {
let roots = chebyshev_t_roots(4).expect("failed");
for &r in &roots {
let val = chebyshev(4, r, true);
assert!(val.abs() < 1e-12, "T_4({r}) = {val}");
}
}
#[test]
fn test_chebyshev_u_roots_are_roots() {
let n = 4;
let roots = chebyshev_u_roots(n).expect("failed");
for &r in &roots {
let theta = r.acos();
let sin_theta = theta.sin();
let val = if sin_theta.abs() < 1e-15 {
((n + 1) as f64) * ((n as f64 + 1.0) * theta).cos() / theta.cos()
} else {
((n as f64 + 1.0) * theta).sin() / sin_theta
};
assert!(val.abs() < 1e-10, "U_{n}({r}) = {val}");
}
}
#[test]
fn test_hermite_roots_degree2() {
let roots = hermite_roots(2).expect("failed");
let expected = (0.5_f64).sqrt();
assert!((roots[0] + expected).abs() < 1e-12);
assert!((roots[1] - expected).abs() < 1e-12);
}
#[test]
fn test_hermite_roots_are_roots() {
let roots = hermite_roots(5).expect("failed");
for &r in &roots {
let val = hermite(5, r);
assert!(val.abs() < 1e-6, "H_5({r}) = {val}");
}
}
#[test]
fn test_hermite_prob_roots_are_roots() {
let roots = hermite_prob_roots(4).expect("failed");
for &r in &roots {
let val = hermite_prob(4, r);
assert!(val.abs() < 1e-8, "He_4({r}) = {val}");
}
}
#[test]
fn test_laguerre_roots_positive() {
let roots = laguerre_roots(4).expect("failed");
for &r in &roots {
assert!(r > 0.0, "Laguerre root should be positive: {r}");
}
}
#[test]
fn test_laguerre_roots_are_roots() {
let roots = laguerre_roots(4).expect("failed");
for &r in &roots {
let val = laguerre(4, r);
assert!(val.abs() < 1e-8, "L_4({r}) = {val}");
}
}
#[test]
fn test_jacobi_roots_in_range() {
let roots = jacobi_roots(5, 1.0, 2.0).expect("failed");
for &r in &roots {
assert!(
r > -1.0 - 1e-10 && r < 1.0 + 1e-10,
"root out of range: {r}"
);
}
}
#[test]
fn test_jacobi_roots_legendre_case() {
let j_roots = jacobi_roots(3, 0.0, 0.0).expect("failed");
let l_roots = legendre_roots(3).expect("failed");
for (jr, lr) in j_roots.iter().zip(l_roots.iter()) {
assert!(
(jr - lr).abs() < 1e-10,
"Jacobi(0,0) roots should match Legendre"
);
}
}
#[test]
fn test_gegenbauer_roots_count() {
let roots = gegenbauer_roots(4, 1.5).expect("failed");
assert_eq!(roots.len(), 4);
}
#[test]
fn test_root_error_degree0() {
assert!(legendre_roots(0).is_err());
assert!(hermite_roots(0).is_err());
assert!(laguerre_roots(0).is_err());
}
#[test]
fn test_legendre_batch() {
let points = vec![0.0f64, 0.5, 1.0];
let vals = legendre_batch(2, &points);
assert_relative_eq!(vals[0], -0.5, epsilon = 1e-14);
assert_relative_eq!(vals[2], 1.0, epsilon = 1e-14);
}
#[test]
fn test_chebyshev_t_batch() {
let points = vec![0.0f64, 0.5, 1.0];
let vals = chebyshev_t_batch(2, &points);
assert_relative_eq!(vals[0], -1.0, epsilon = 1e-14);
assert_relative_eq!(vals[2], 1.0, epsilon = 1e-14);
}
#[test]
fn test_hermite_batch() {
let points = vec![0.0f64, 0.5, 1.0];
let vals = hermite_batch(2, &points);
assert_relative_eq!(vals[0], -2.0, epsilon = 1e-14);
assert_relative_eq!(vals[2], 2.0, epsilon = 1e-14);
}
#[test]
fn test_legendre_coefficients_p0() {
let c = legendre_coefficients(0);
assert_eq!(c.len(), 1);
assert_relative_eq!(c[0], 1.0, epsilon = 1e-14);
}
#[test]
fn test_legendre_coefficients_p1() {
let c = legendre_coefficients(1);
assert_eq!(c.len(), 2);
assert_relative_eq!(c[0], 0.0, epsilon = 1e-14);
assert_relative_eq!(c[1], 1.0, epsilon = 1e-14);
}
#[test]
fn test_legendre_coefficients_p2() {
let c = legendre_coefficients(2);
assert_relative_eq!(c[0], -0.5, epsilon = 1e-14);
assert!(c[1].abs() < 1e-14);
assert_relative_eq!(c[2], 1.5, epsilon = 1e-14);
}
#[test]
fn test_chebyshev_t_coefficients_t2() {
let c = chebyshev_t_coefficients(2);
assert_relative_eq!(c[0], -1.0, epsilon = 1e-14);
assert!(c[1].abs() < 1e-14);
assert_relative_eq!(c[2], 2.0, epsilon = 1e-14);
}
#[test]
fn test_hermite_coefficients_h2() {
let c = hermite_coefficients(2);
assert_relative_eq!(c[0], -2.0, epsilon = 1e-14);
assert!(c[1].abs() < 1e-14);
assert_relative_eq!(c[2], 4.0, epsilon = 1e-14);
}
#[test]
fn test_hermite_prob_coefficients_he2() {
let c = hermite_prob_coefficients(2);
assert_relative_eq!(c[0], -1.0, epsilon = 1e-14);
assert!(c[1].abs() < 1e-14);
assert_relative_eq!(c[2], 1.0, epsilon = 1e-14);
}
#[test]
fn test_legendre_kth_derivative_k0() {
assert_relative_eq!(
legendre_kth_derivative(3, 0, 0.5f64),
legendre(3, 0.5f64),
epsilon = 1e-10
);
}
#[test]
fn test_legendre_kth_derivative_k1() {
let d1 = legendre_kth_derivative(2, 1, 0.5f64);
assert_relative_eq!(d1, 1.5, epsilon = 1e-8);
}
#[test]
fn test_legendre_kth_derivative_k2() {
let d2 = legendre_kth_derivative(2, 2, 0.5f64);
assert_relative_eq!(d2, 3.0, epsilon = 1e-6);
}
#[test]
fn test_hermite_kth_derivative_exact() {
let d3 = hermite_kth_derivative(3, 3, 0.5f64);
assert_relative_eq!(d3, 48.0, epsilon = 1e-10);
}
#[test]
fn test_hermite_kth_derivative_identity() {
let x = 0.7f64;
let d2 = hermite_kth_derivative(4, 2, x);
let expected = 4.0 * (4.0 * 3.0) * hermite(2, x);
assert_relative_eq!(d2, expected, epsilon = 1e-10);
}
#[test]
fn test_hermite_prob_kth_derivative_identity() {
let x = 0.7f64;
let d2 = hermite_prob_kth_derivative(4, 2, x);
let expected = (4.0 * 3.0) * hermite_prob(2, x);
assert_relative_eq!(d2, expected, epsilon = 1e-10);
}
#[test]
fn test_kth_derivative_beyond_degree_is_zero() {
assert_relative_eq!(legendre_kth_derivative(3, 4, 0.5f64), 0.0, epsilon = 1e-14);
assert_relative_eq!(hermite_kth_derivative(2, 3, 0.5f64), 0.0, epsilon = 1e-14);
}
#[test]
fn test_legendre_all() {
let vals = legendre_all(4, 0.5f64);
assert_eq!(vals.len(), 5);
for (i, &v) in vals.iter().enumerate() {
let expected = legendre(i, 0.5f64);
assert_relative_eq!(v, expected, epsilon = 1e-12);
}
}
#[test]
fn test_chebyshev_t_all() {
let vals = chebyshev_t_all(4, 0.3f64);
assert_eq!(vals.len(), 5);
for (i, &v) in vals.iter().enumerate() {
let expected = chebyshev(i, 0.3f64, true);
assert_relative_eq!(v, expected, epsilon = 1e-12);
}
}
#[test]
fn test_hermite_all() {
let vals = hermite_all(4, 0.5f64);
assert_eq!(vals.len(), 5);
for (i, &v) in vals.iter().enumerate() {
let expected = hermite(i, 0.5f64);
assert_relative_eq!(v, expected, epsilon = 1e-10);
}
}
#[test]
fn test_clenshaw_chebyshev_constant() {
let coeffs = [3.0f64];
assert_relative_eq!(clenshaw_chebyshev(&coeffs, 0.5), 3.0, epsilon = 1e-14);
}
#[test]
fn test_clenshaw_chebyshev_linear() {
let coeffs = [2.0f64, 3.0];
assert_relative_eq!(clenshaw_chebyshev(&coeffs, 0.5), 3.5, epsilon = 1e-14);
}
#[test]
fn test_clenshaw_chebyshev_quadratic() {
let coeffs = [1.0f64, 0.0, -1.0];
assert_relative_eq!(clenshaw_chebyshev(&coeffs, 0.5), 1.5, epsilon = 1e-14);
}
#[test]
fn test_clenshaw_legendre_constant() {
let coeffs = [5.0f64];
assert_relative_eq!(clenshaw_legendre(&coeffs, 0.3), 5.0, epsilon = 1e-14);
}
#[test]
fn test_clenshaw_empty() {
let coeffs: [f64; 0] = [];
assert_relative_eq!(clenshaw_chebyshev(&coeffs, 0.5), 0.0, epsilon = 1e-14);
assert_relative_eq!(clenshaw_legendre(&coeffs, 0.5), 0.0, epsilon = 1e-14);
}
#[test]
fn test_eval_three_term_legendre() {
let val = eval_three_term_recurrence(3, 0.5f64, |k| {
let k_f = k as f64;
let a = (2.0 * k_f + 1.0) / (k_f + 1.0);
let b = 0.0;
let c = k_f / (k_f + 1.0);
(a, b, c)
});
assert_relative_eq!(val, legendre(3, 0.5f64), epsilon = 1e-12);
}
#[test]
fn test_eval_all_degrees_basic() {
let vals = eval_all_degrees(3, 0.5f64, |k| {
let k_f = k as f64;
let a = (2.0 * k_f + 1.0) / (k_f + 1.0);
let b = 0.0;
let c = k_f / (k_f + 1.0);
(a, b, c)
});
assert_eq!(vals.len(), 4);
assert_relative_eq!(vals[0], 1.0, epsilon = 1e-14);
assert_relative_eq!(vals[1], 0.5, epsilon = 1e-14);
}
}