use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use num_traits::{Float, One, Zero};
use std::ops::{Add, Div, Mul, Sub};
use super::core::Polynomial;
pub struct OrthogonalPolynomials;
impl OrthogonalPolynomials {
pub fn chebyshev_t<T>(n: usize) -> Polynomial<T>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
if n == 0 {
return Polynomial::one();
}
if n == 1 {
return Polynomial::new(vec![T::one(), T::zero()]);
}
let mut t_prev: Polynomial<T> = Polynomial::one();
let mut t_curr: Polynomial<T> = Polynomial::new(vec![T::one(), T::zero()]);
for _ in 2..=n {
let two_x = Polynomial::new(vec![T::from(2), T::zero()]);
let two_x_t_curr = two_x * t_curr.clone();
let t_next = two_x_t_curr - t_prev;
t_prev = t_curr;
t_curr = t_next;
}
t_curr
}
pub fn chebyshev_u<T>(n: usize) -> Polynomial<T>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
if n == 0 {
return Polynomial::one();
}
if n == 1 {
return Polynomial::new(vec![T::from(2), T::zero()]);
}
let mut u_prev: Polynomial<T> = Polynomial::one();
let mut u_curr: Polynomial<T> = Polynomial::new(vec![T::from(2), T::zero()]);
for _ in 2..=n {
let two_x = Polynomial::new(vec![T::from(2), T::zero()]);
let two_x_u_curr = two_x * u_curr.clone();
let u_next = two_x_u_curr - u_prev;
u_prev = u_curr;
u_curr = u_next;
}
u_curr
}
pub fn legendre<T>(n: usize) -> Polynomial<T>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
if n == 0 {
return Polynomial::one();
}
if n == 1 {
return Polynomial::new(vec![T::one(), T::zero()]);
}
let mut p_prev: Polynomial<T> = Polynomial::one();
let mut p_curr: Polynomial<T> = Polynomial::new(vec![T::one(), T::zero()]);
for k in 1..n {
let k_plus_1 = T::from((k + 1) as i32);
let two_k_plus_1 = T::from((2 * k + 1) as i32);
let k_t = T::from(k as i32);
let x_poly = Polynomial::new(vec![T::one(), T::zero()]);
let mut term1 = x_poly * p_curr.clone();
term1.coefficients = term1
.coefficients
.iter()
.map(|c| c.clone() * two_k_plus_1.clone())
.collect();
let mut term2 = p_prev.clone();
term2.coefficients = term2
.coefficients
.iter()
.map(|c| c.clone() * k_t.clone())
.collect();
let mut p_next = term1 - term2;
p_next.coefficients = p_next
.coefficients
.iter()
.map(|c| c.clone() / k_plus_1.clone())
.collect();
p_prev = p_curr;
p_curr = p_next;
}
p_curr
}
pub fn hermite<T>(n: usize) -> Polynomial<T>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
if n == 0 {
return Polynomial::one();
}
if n == 1 {
return Polynomial::new(vec![T::from(2), T::zero()]);
}
let mut h_prev: Polynomial<T> = Polynomial::one();
let mut h_curr: Polynomial<T> = Polynomial::new(vec![T::from(2), T::zero()]);
for k in 1..n {
let two_k = T::from((2 * k) as i32);
let two_x = Polynomial::new(vec![T::from(2), T::zero()]);
let two_x_h_curr = two_x * h_curr.clone();
let mut term2 = h_prev.clone();
term2.coefficients = term2
.coefficients
.iter()
.map(|c| c.clone() * two_k.clone())
.collect();
let h_next = two_x_h_curr - term2;
h_prev = h_curr;
h_curr = h_next;
}
h_curr
}
pub fn laguerre<T>(n: usize) -> Polynomial<T>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
if n == 0 {
return Polynomial::one();
}
if n == 1 {
return Polynomial::new(vec![T::from(-1), T::one()]);
}
let mut l_prev: Polynomial<T> = Polynomial::one();
let mut l_curr: Polynomial<T> = Polynomial::new(vec![T::from(-1), T::one()]);
for k in 1..n {
let k_plus_1 = T::from((k + 1) as i32);
let two_k_plus_1 = T::from((2 * k + 1) as i32);
let k_t = T::from(k as i32);
let _x_poly = Polynomial::new(vec![T::one(), T::zero()]);
let two_k_plus_1_minus_x = Polynomial::new(vec![T::from(-1), two_k_plus_1.clone()]);
let term1 = two_k_plus_1_minus_x * l_curr.clone();
let mut term2 = l_prev.clone();
term2.coefficients = term2
.coefficients
.iter()
.map(|c| c.clone() * k_t.clone())
.collect();
let mut l_next = term1 - term2;
l_next.coefficients = l_next
.coefficients
.iter()
.map(|c| c.clone() / k_plus_1.clone())
.collect();
l_prev = l_curr;
l_curr = l_next;
}
l_curr
}
}
pub fn polychebyshev<T>(degree: usize, kind: u8) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
let poly = match kind {
1 => OrthogonalPolynomials::chebyshev_t::<T>(degree),
2 => OrthogonalPolynomials::chebyshev_u::<T>(degree),
_ => {
return Err(NumRs2Error::InvalidOperation(
"Kind must be 1 (first kind) or 2 (second kind)".to_string(),
))
}
};
Ok(Array::from_vec(poly.coefficients().to_vec()))
}
pub fn polylegendre<T>(degree: usize) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
let poly = OrthogonalPolynomials::legendre::<T>(degree);
Ok(Array::from_vec(poly.coefficients().to_vec()))
}
pub fn polyhermite<T>(degree: usize) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
let poly = OrthogonalPolynomials::hermite::<T>(degree);
Ok(Array::from_vec(poly.coefficients().to_vec()))
}
pub fn polylaguerre<T>(degree: usize) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ From<i32>
+ PartialEq
+ std::ops::Neg<Output = T>,
{
let poly = OrthogonalPolynomials::laguerre::<T>(degree);
Ok(Array::from_vec(poly.coefficients().to_vec()))
}
pub fn polyjacobi<T>(degree: usize, alpha: T, beta: T) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ PartialEq
+ std::ops::Neg<Output = T>
+ Float,
{
use num_traits::NumCast;
if alpha <= -T::one() || beta <= -T::one() {
return Err(NumRs2Error::InvalidOperation(
"Jacobi polynomial requires alpha > -1 and beta > -1".to_string(),
));
}
if degree == 0 {
return Ok(Array::from_vec(vec![T::one()]));
}
let two: T = NumCast::from(2).expect("2 should convert to numeric type");
if degree == 1 {
let a = (alpha + beta + two) / two;
let b = (alpha - beta) / two;
return Ok(Array::from_vec(vec![a, b]));
}
let mut p_prev = Polynomial::<T>::one();
let a1 = (alpha + beta + two) / two;
let b1 = (alpha - beta) / two;
let mut p_curr = Polynomial::new(vec![a1, b1]);
for n in 1..(degree as i32) {
let n_t: T = NumCast::from(n).expect("n should convert to numeric type");
let two_n: T = NumCast::from(2 * n).expect("2*n should convert to numeric type");
let n_plus_alpha_beta = n_t + alpha + beta;
let denom =
two * (n_t + T::one()) * (n_plus_alpha_beta + T::one()) * (two_n + alpha + beta);
let a_n = (two_n + alpha + beta + T::one())
* ((two_n + alpha + beta + two) * (two_n + alpha + beta) * T::one()
+ (alpha * alpha - beta * beta))
/ denom;
let b_n = two * (n_t + alpha) * (n_t + beta) * (two_n + alpha + beta + two) / denom;
let x_poly = Polynomial::new(vec![T::one(), T::zero()]);
let mut term1 = x_poly * p_curr.clone();
term1.coefficients = term1.coefficients.iter().map(|c| *c * a_n).collect();
let mut term2 = p_prev.clone();
term2.coefficients = term2.coefficients.iter().map(|c| *c * b_n).collect();
let p_next = term1 - term2;
p_prev = p_curr;
p_curr = p_next;
}
Ok(Array::from_vec(p_curr.coefficients().to_vec()))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_chebyshev_polynomials() {
let t0 = OrthogonalPolynomials::chebyshev_t::<f64>(0);
let t1 = OrthogonalPolynomials::chebyshev_t::<f64>(1);
let t2 = OrthogonalPolynomials::chebyshev_t::<f64>(2);
assert_eq!(t0.coefficients(), &[1.0]);
assert_eq!(t1.coefficients(), &[1.0, 0.0]);
assert_eq!(t2.coefficients(), &[2.0, 0.0, -1.0]);
assert_relative_eq!(t2.evaluate(0.0), -1.0, epsilon = 1e-10);
assert_relative_eq!(t2.evaluate(1.0), 1.0, epsilon = 1e-10);
assert_relative_eq!(t2.evaluate(0.5), -0.5, epsilon = 1e-10);
}
#[test]
fn test_legendre_polynomials() {
let p0 = OrthogonalPolynomials::legendre::<f64>(0);
let p1 = OrthogonalPolynomials::legendre::<f64>(1);
let p2 = OrthogonalPolynomials::legendre::<f64>(2);
assert_eq!(p0.coefficients(), &[1.0]);
assert_eq!(p1.coefficients(), &[1.0, 0.0]);
assert_relative_eq!(p2.evaluate(0.0), -0.5, epsilon = 1e-10);
assert_relative_eq!(p2.evaluate(1.0), 1.0, epsilon = 1e-10);
}
#[test]
fn test_polyjacobi_degree_0() {
let j0 = polyjacobi(0, 1.0, 1.0).expect("Jacobi polynomial degree 0 should succeed");
assert_eq!(j0.len(), 1);
assert_relative_eq!(j0.to_vec()[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_polyjacobi_degree_1() {
let j1 = polyjacobi(1, 0.0, 0.0).expect("Jacobi polynomial degree 1 should succeed");
let data = j1.to_vec();
assert_eq!(data.len(), 2);
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 0.0, epsilon = 1e-10);
}
}