use std::fmt::{self, Display};
use crate::taylor_ops;
use crate::Float;
#[derive(Clone, Copy, Debug)]
pub struct Taylor<F: Float, const K: usize> {
pub coeffs: [F; K],
}
impl<F: Float, const K: usize> Default for Taylor<F, K> {
fn default() -> Self {
Taylor {
coeffs: [F::zero(); K],
}
}
}
impl<F: Float, const K: usize> Display for Taylor<F, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.coeffs[0])?;
for (i, c) in self.coeffs.iter().enumerate().skip(1) {
write!(f, " + {c}·t^{i}")?;
}
Ok(())
}
}
impl<F: Float, const K: usize> From<F> for Taylor<F, K> {
#[inline]
fn from(val: F) -> Self {
Taylor::constant(val)
}
}
macro_rules! taylor_elementals {
($( $(#[$doc:meta])* $name:ident => $kernel:ident / $scratch:tt; )+) => {$(
$(#[$doc])*
#[inline]
pub fn $name(self) -> Self {
let mut c = [F::zero(); K];
taylor_elementals!(@call $kernel, self, c, $scratch);
Taylor { coeffs: c }
}
)+};
(@call $kernel:ident, $self:ident, $c:ident, 0) => {
taylor_ops::$kernel(&$self.coeffs, &mut $c)
};
(@call $kernel:ident, $self:ident, $c:ident, 1) => {{
let mut s = [F::zero(); K];
taylor_ops::$kernel(&$self.coeffs, &mut $c, &mut s);
}};
(@call $kernel:ident, $self:ident, $c:ident, 2) => {{
let mut s1 = [F::zero(); K];
let mut s2 = [F::zero(); K];
taylor_ops::$kernel(&$self.coeffs, &mut $c, &mut s1, &mut s2);
}};
}
impl<F: Float, const K: usize> Taylor<F, K> {
#[inline]
pub fn new(coeffs: [F; K]) -> Self {
Taylor { coeffs }
}
#[inline]
pub fn constant(val: F) -> Self {
let mut coeffs = [F::zero(); K];
coeffs[0] = val;
Taylor { coeffs }
}
#[inline]
pub fn variable(val: F) -> Self {
let mut coeffs = [F::zero(); K];
coeffs[0] = val;
if K > 1 {
coeffs[1] = F::one();
}
Taylor { coeffs }
}
#[inline]
pub fn value(&self) -> F {
self.coeffs[0]
}
#[inline]
pub fn coeff(&self, k: usize) -> F {
self.coeffs[k]
}
#[inline]
pub fn derivative(&self, k: usize) -> F {
let mut result = self.coeffs[k];
for i in 2..=k {
result = result * F::from(i).unwrap();
}
result
}
#[inline]
pub fn eval_at(&self, h: F) -> F {
let mut val = self.coeffs[K - 1];
for k in (0..K - 1).rev() {
val = val * h + self.coeffs[k];
}
val
}
taylor_elementals! {
recip => taylor_recip / 0;
sqrt => taylor_sqrt / 0;
cbrt => taylor_cbrt / 2;
exp => taylor_exp / 0;
exp2 => taylor_exp2 / 1;
exp_m1 => taylor_exp_m1 / 0;
ln => taylor_ln / 0;
log2 => taylor_log2 / 0;
log10 => taylor_log10 / 0;
ln_1p => taylor_ln_1p / 1;
tan => taylor_tan / 1;
asin => taylor_asin / 2;
acos => taylor_acos / 2;
atan => taylor_atan / 2;
tanh => taylor_tanh / 1;
asinh => taylor_asinh / 2;
acosh => taylor_acosh / 2;
atanh => taylor_atanh / 2;
}
#[inline]
pub fn powi(self, n: i32) -> Self {
let mut c = [F::zero(); K];
let mut s1 = [F::zero(); K];
let mut s2 = [F::zero(); K];
taylor_ops::taylor_powi(&self.coeffs, n, &mut c, &mut s1, &mut s2);
Taylor { coeffs: c }
}
#[inline]
pub fn powf(self, n: Self) -> Self {
let mut c = [F::zero(); K];
let mut s1 = [F::zero(); K];
let mut s2 = [F::zero(); K];
taylor_ops::taylor_powf(&self.coeffs, &n.coeffs, &mut c, &mut s1, &mut s2);
Taylor { coeffs: c }
}
#[inline]
pub fn log(self, base: Self) -> Self {
self.ln() / base.ln()
}
#[inline]
pub fn sin(self) -> Self {
let mut s = [F::zero(); K];
let mut co = [F::zero(); K];
taylor_ops::taylor_sin_cos(&self.coeffs, &mut s, &mut co);
Taylor { coeffs: s }
}
#[inline]
pub fn cos(self) -> Self {
let mut s = [F::zero(); K];
let mut co = [F::zero(); K];
taylor_ops::taylor_sin_cos(&self.coeffs, &mut s, &mut co);
Taylor { coeffs: co }
}
#[inline]
pub fn sin_cos(self) -> (Self, Self) {
let mut s = [F::zero(); K];
let mut co = [F::zero(); K];
taylor_ops::taylor_sin_cos(&self.coeffs, &mut s, &mut co);
(Taylor { coeffs: s }, Taylor { coeffs: co })
}
#[inline]
pub fn atan2(self, other: Self) -> Self {
let mut c = [F::zero(); K];
let mut s1 = [F::zero(); K];
let mut s2 = [F::zero(); K];
let mut s3 = [F::zero(); K];
taylor_ops::taylor_atan2(
&self.coeffs,
&other.coeffs,
&mut c,
&mut s1,
&mut s2,
&mut s3,
);
Taylor { coeffs: c }
}
#[inline]
pub fn sinh(self) -> Self {
let mut sh = [F::zero(); K];
let mut ch = [F::zero(); K];
taylor_ops::taylor_sinh_cosh(&self.coeffs, &mut sh, &mut ch);
Taylor { coeffs: sh }
}
#[inline]
pub fn cosh(self) -> Self {
let mut sh = [F::zero(); K];
let mut ch = [F::zero(); K];
taylor_ops::taylor_sinh_cosh(&self.coeffs, &mut sh, &mut ch);
Taylor { coeffs: ch }
}
#[inline]
pub fn abs(self) -> Self {
let mut coeffs = self.coeffs;
let sign = if self.coeffs[0] != F::zero() {
self.coeffs[0].signum()
} else if let Some(k) = (1..K).find(|&k| self.coeffs[k] != F::zero()) {
self.coeffs[k].signum()
} else {
F::one()
};
for c in &mut coeffs {
*c = *c * sign;
}
Taylor { coeffs }
}
#[inline]
pub fn signum(self) -> Self {
Taylor::constant(self.coeffs[0].signum())
}
#[inline]
pub fn floor(self) -> Self {
Self::constant(self.coeffs[0].floor())
}
#[inline]
pub fn ceil(self) -> Self {
Self::constant(self.coeffs[0].ceil())
}
#[inline]
pub fn round(self) -> Self {
Self::constant(self.coeffs[0].round())
}
#[inline]
pub fn trunc(self) -> Self {
Self::constant(self.coeffs[0].trunc())
}
#[inline]
pub fn fract(self) -> Self {
let mut coeffs = self.coeffs;
coeffs[0] = self.coeffs[0].fract();
Taylor { coeffs }
}
#[inline]
pub fn mul_add(self, a: Self, b: Self) -> Self {
self * a + b
}
#[inline]
pub fn hypot(self, other: Self) -> Self {
let mut c = [F::zero(); K];
let mut s1 = [F::zero(); K];
let mut s2 = [F::zero(); K];
taylor_ops::taylor_hypot(&self.coeffs, &other.coeffs, &mut c, &mut s1, &mut s2);
Taylor { coeffs: c }
}
#[inline]
pub fn max(self, other: Self) -> Self {
if self.coeffs[0] >= other.coeffs[0] || other.coeffs[0].is_nan() {
self
} else {
other
}
}
#[inline]
pub fn min(self, other: Self) -> Self {
if self.coeffs[0] <= other.coeffs[0] || other.coeffs[0].is_nan() {
self
} else {
other
}
}
}