use crate::defs::WORD_BIT_SIZE;
use crate::Error;
use crate::ExactNum;
use crate::RoundingMode;
pub const ORTHOPOLY_N_MAX: usize = 256;
fn work_p(p: usize) -> usize {
p.saturating_add(WORD_BIT_SIZE)
}
fn op_nan() -> ExactNum {
ExactNum::nan(Some(Error::InvalidArgument))
}
fn finite(x: &ExactNum) -> bool {
!x.is_nan() && !x.is_inf()
}
fn n_ok(n: usize) -> bool {
n <= ORTHOPOLY_N_MAX
}
fn finish(mut y: ExactNum, p: usize, rm: RoundingMode) -> ExactNum {
let _ = y.set_precision(p, rm);
y
}
impl ExactNum {
pub fn hermite_he(&self, n: usize, p: usize, rm: RoundingMode) -> Self {
if !n_ok(n) || !finite(self) {
return op_nan();
}
let wrk = work_p(p);
if n == 0 {
return finish(ExactNum::from_u8(1, wrk), p, rm);
}
if n == 1 {
return finish(self.clone(), p, rm);
}
let mut prev = ExactNum::from_u8(1, wrk);
let mut cur = self.clone();
let _ = cur.set_precision(wrk, RoundingMode::None);
for k in 1..n {
let kf = ExactNum::from_u32(k as u32, wrk);
let next = self.mul(&cur, wrk, RoundingMode::None).sub(
&kf.mul(&prev, wrk, RoundingMode::None),
wrk,
RoundingMode::None,
);
prev = cur;
cur = next;
}
finish(cur, p, rm)
}
pub fn hermite_h(&self, n: usize, p: usize, rm: RoundingMode) -> Self {
if !n_ok(n) || !finite(self) {
return op_nan();
}
let wrk = work_p(p);
let two = ExactNum::from_u8(2, wrk);
if n == 0 {
return finish(ExactNum::from_u8(1, wrk), p, rm);
}
if n == 1 {
return finish(two.mul(self, wrk, RoundingMode::None), p, rm);
}
let mut prev = ExactNum::from_u8(1, wrk);
let mut cur = two.mul(self, wrk, RoundingMode::None);
for k in 1..n {
let two_k = ExactNum::from_u32((2 * k) as u32, wrk);
let next = two
.mul(self, wrk, RoundingMode::None)
.mul(&cur, wrk, RoundingMode::None)
.sub(
&two_k.mul(&prev, wrk, RoundingMode::None),
wrk,
RoundingMode::None,
);
prev = cur;
cur = next;
}
finish(cur, p, rm)
}
pub fn laguerre(&self, n: usize, p: usize, rm: RoundingMode) -> Self {
self.gen_laguerre(n, &ExactNum::new(p), p, rm)
}
pub fn gen_laguerre(&self, n: usize, alpha: &Self, p: usize, rm: RoundingMode) -> Self {
if !n_ok(n) || !finite(self) || !finite(alpha) {
return op_nan();
}
let wrk = work_p(p);
let one = ExactNum::from_u8(1, wrk);
if n == 0 {
return finish(one, p, rm);
}
if n == 1 {
return finish(
one.add(alpha, wrk, RoundingMode::None)
.sub(self, wrk, RoundingMode::None),
p,
rm,
);
}
let mut prev = one.clone();
let mut cur = one
.add(alpha, wrk, RoundingMode::None)
.sub(self, wrk, RoundingMode::None);
for k in 1..n {
let kf = ExactNum::from_u32(k as u32, wrk);
let two_k_1 = ExactNum::from_u32((2 * k + 1) as u32, wrk);
let coeff =
two_k_1
.add(alpha, wrk, RoundingMode::None)
.sub(self, wrk, RoundingMode::None);
let k_a = kf.add(alpha, wrk, RoundingMode::None);
let num = coeff.mul(&cur, wrk, RoundingMode::None).sub(
&k_a.mul(&prev, wrk, RoundingMode::None),
wrk,
RoundingMode::None,
);
let den = ExactNum::from_u32((k + 1) as u32, wrk);
let next = num.div(&den, wrk, RoundingMode::None);
prev = cur;
cur = next;
}
finish(cur, p, rm)
}
pub fn chebyshev_t(&self, n: usize, p: usize, rm: RoundingMode) -> Self {
if !n_ok(n) || !finite(self) {
return op_nan();
}
let wrk = work_p(p);
if n == 0 {
return finish(ExactNum::from_u8(1, wrk), p, rm);
}
if n == 1 {
return finish(self.clone(), p, rm);
}
let two = ExactNum::from_u8(2, wrk);
let mut prev = ExactNum::from_u8(1, wrk);
let mut cur = self.clone();
let _ = cur.set_precision(wrk, RoundingMode::None);
for _ in 1..n {
let next = two
.mul(self, wrk, RoundingMode::None)
.mul(&cur, wrk, RoundingMode::None)
.sub(&prev, wrk, RoundingMode::None);
prev = cur;
cur = next;
}
finish(cur, p, rm)
}
pub fn chebyshev_u(&self, n: usize, p: usize, rm: RoundingMode) -> Self {
if !n_ok(n) || !finite(self) {
return op_nan();
}
let wrk = work_p(p);
let two = ExactNum::from_u8(2, wrk);
if n == 0 {
return finish(ExactNum::from_u8(1, wrk), p, rm);
}
if n == 1 {
return finish(two.mul(self, wrk, RoundingMode::None), p, rm);
}
let mut prev = ExactNum::from_u8(1, wrk);
let mut cur = two.mul(self, wrk, RoundingMode::None);
for _ in 1..n {
let next = two
.mul(self, wrk, RoundingMode::None)
.mul(&cur, wrk, RoundingMode::None)
.sub(&prev, wrk, RoundingMode::None);
prev = cur;
cur = next;
}
finish(cur, p, rm)
}
pub fn gegenbauer(&self, n: usize, lambda: &Self, p: usize, rm: RoundingMode) -> Self {
if !n_ok(n) || !finite(self) || !finite(lambda) {
return op_nan();
}
let wrk = work_p(p);
let one = ExactNum::from_u8(1, wrk);
if n == 0 {
return finish(one, p, rm);
}
let two = ExactNum::from_u8(2, wrk);
if n == 1 {
return finish(
two.mul(lambda, wrk, RoundingMode::None)
.mul(self, wrk, RoundingMode::None),
p,
rm,
);
}
let mut prev = one.clone();
let mut cur = two
.mul(lambda, wrk, RoundingMode::None)
.mul(self, wrk, RoundingMode::None);
for k in 1..n {
let kf = ExactNum::from_u32(k as u32, wrk);
let two_n_l = two.mul(
&kf.add(lambda, wrk, RoundingMode::None),
wrk,
RoundingMode::None,
);
let n_2l_1 = kf
.add(
&two.mul(lambda, wrk, RoundingMode::None),
wrk,
RoundingMode::None,
)
.sub(&one, wrk, RoundingMode::None);
let num = two_n_l
.mul(self, wrk, RoundingMode::None)
.mul(&cur, wrk, RoundingMode::None)
.sub(
&n_2l_1.mul(&prev, wrk, RoundingMode::None),
wrk,
RoundingMode::None,
);
let den = ExactNum::from_u32((k + 1) as u32, wrk);
let next = num.div(&den, wrk, RoundingMode::None);
prev = cur;
cur = next;
}
finish(cur, p, rm)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Consts;
fn gold_p() -> (usize, RoundingMode) {
(256, RoundingMode::ToEven)
}
#[test]
fn orthopoly_he4_laguerre_t5_gegenbauer_recurrence() {
let (p, rm) = gold_p();
let mut cc = Consts::new().expect("consts");
let zero = ExactNum::new(p);
let one = ExactNum::from_u8(1, p);
let two = ExactNum::from_u8(2, p);
let three = ExactNum::from_u8(3, p);
let half = one.div(&two, p, rm);
assert_eq!(zero.hermite_he(4, p, rm).cmp(&three), Some(0));
assert_eq!(zero.laguerre(3, p, rm).cmp(&one), Some(0));
assert_eq!(zero.gen_laguerre(3, &zero, p, rm).cmp(&one), Some(0));
let pi = cc.pi(p, rm);
let five = ExactNum::from_u8(5, p);
let c = pi.div(&five, p, rm).cos(p, rm, &mut cc);
let t5 = c.chebyshev_t(5, p, rm);
let neg_one = one.neg();
assert_eq!(t5.cmp(&neg_one), Some(0));
let g1 = half.gegenbauer(2, &one, p, rm);
let u2 = half.chebyshev_u(2, p, rm);
let four_x2_m1 = ExactNum::from_u8(4, p)
.mul(&half, p, rm)
.mul(&half, p, rm)
.sub(&one, p, rm);
assert_eq!(g1.cmp(&four_x2_m1), Some(0));
assert_eq!(g1.cmp(&u2), Some(0));
let lam_half = half.clone();
let g_leg = half.gegenbauer(2, &lam_half, p, rm);
let three_x2_m1 = three.mul(&half, p, rm).mul(&half, p, rm).sub(&one, p, rm);
let two_p2 = g_leg.mul(&two, p, rm);
assert_eq!(two_p2.cmp(&three_x2_m1), Some(0));
let t6 = half.chebyshev_t(6, p, rm);
let t5h = half.chebyshev_t(5, p, rm);
let t4h = half.chebyshev_t(4, p, rm);
let rec = two.mul(&half, p, rm).mul(&t5h, p, rm).sub(&t4h, p, rm);
assert_eq!(t6.cmp(&rec), Some(0));
assert!(zero.hermite_he(ORTHOPOLY_N_MAX + 1, p, rm).is_nan());
assert_eq!(
zero.hermite_h(4, p, rm).cmp(&ExactNum::from_u8(12, p)),
Some(0)
);
}
}