use std::cell::Cell;
use std::fmt::{self, Display};
use crate::taylor_ops;
use crate::Float;
pub const CONSTANT: u32 = u32::MAX;
pub struct TaylorArena<F: Float> {
data: Vec<F>,
degree: usize,
count: u32,
scratch: Vec<F>,
}
impl<F: Float> TaylorArena<F> {
#[must_use]
pub fn new(degree: usize) -> Self {
TaylorArena {
data: Vec::new(),
degree,
count: 0,
scratch: Vec::new(),
}
}
#[inline]
#[must_use]
pub fn degree(&self) -> usize {
self.degree
}
#[inline]
pub fn allocate(&mut self) -> u32 {
let idx = self.count;
self.count += 1;
self.data
.resize(self.count as usize * self.degree, F::zero());
idx
}
#[inline]
#[must_use]
pub fn coeffs(&self, index: u32) -> &[F] {
let start = index as usize * self.degree;
&self.data[start..start + self.degree]
}
#[inline]
pub fn coeffs_mut(&mut self, index: u32) -> &mut [F] {
let start = index as usize * self.degree;
&mut self.data[start..start + self.degree]
}
pub fn clear(&mut self) {
self.data.clear();
self.count = 0;
}
fn split_output(&mut self, out_idx: u32, slabs: usize) -> (&[F], &mut [F], &mut [F]) {
debug_assert_eq!(out_idx + 1, self.count, "output must be the newest entry");
let deg = self.degree;
let need = slabs * deg;
if self.scratch.len() < need {
self.scratch.resize(need, F::zero());
}
self.scratch[..need].fill(F::zero());
let start = out_idx as usize * deg;
let (before, out) = self.data.split_at_mut(start);
(&*before, &mut out[..deg], &mut self.scratch[..need])
}
}
thread_local! {
static TAYLOR_ARENA_F32: Cell<*mut TaylorArena<f32>> = const { Cell::new(std::ptr::null_mut()) };
static TAYLOR_ARENA_F64: Cell<*mut TaylorArena<f64>> = const { Cell::new(std::ptr::null_mut()) };
}
pub trait TaylorArenaLocal: Float {
fn cell() -> &'static std::thread::LocalKey<Cell<*mut TaylorArena<Self>>>;
}
impl TaylorArenaLocal for f32 {
fn cell() -> &'static std::thread::LocalKey<Cell<*mut TaylorArena<Self>>> {
&TAYLOR_ARENA_F32
}
}
impl TaylorArenaLocal for f64 {
fn cell() -> &'static std::thread::LocalKey<Cell<*mut TaylorArena<Self>>> {
&TAYLOR_ARENA_F64
}
}
#[inline]
pub fn with_active_arena<F: TaylorArenaLocal, R>(f: impl FnOnce(&mut TaylorArena<F>) -> R) -> R {
F::cell().with(|cell| {
let ptr = cell.get();
assert!(
!ptr.is_null(),
"No active Taylor arena. Create a TaylorDynGuard first."
);
let arena = unsafe { &mut *ptr };
f(arena)
})
}
pub struct TaylorDynGuard<F: TaylorArenaLocal> {
#[allow(dead_code)]
arena: Box<TaylorArena<F>>,
prev: *mut TaylorArena<F>,
}
impl<F: TaylorArenaLocal> TaylorDynGuard<F> {
#[must_use]
pub fn new(degree: usize) -> Self {
let mut arena = Box::new(TaylorArena::new(degree));
let prev = F::cell().with(|cell| {
let prev = cell.get();
cell.set(&mut *arena as *mut TaylorArena<F>);
prev
});
TaylorDynGuard { arena, prev }
}
}
impl<F: TaylorArenaLocal> Drop for TaylorDynGuard<F> {
fn drop(&mut self) {
F::cell().with(|cell| {
cell.set(self.prev);
});
}
}
#[cfg(any(feature = "stde", feature = "diffop"))]
pub(crate) fn seed_taylor_dyn_jets<F: Float + TaylorArenaLocal>(
x: &[F],
order: usize,
active: &[(usize, usize, F)],
) -> Vec<TaylorDyn<F>> {
(0..x.len())
.map(|i| {
let mut coeffs = vec![F::zero(); order];
coeffs[0] = x[i];
for &(var, slot, value) in active {
if var == i && slot < order {
coeffs[slot] = value;
}
}
TaylorDyn::from_coeffs(&coeffs)
})
.collect()
}
#[derive(Clone, Copy, Debug)]
pub struct TaylorDyn<F: Float> {
pub(crate) value: F,
pub(crate) index: u32,
}
impl<F: Float> From<F> for TaylorDyn<F> {
#[inline]
fn from(val: F) -> Self {
TaylorDyn::constant(val)
}
}
impl<F: Float> TaylorDyn<F> {
#[inline]
pub fn constant(value: F) -> Self {
TaylorDyn {
value,
index: CONSTANT,
}
}
}
macro_rules! taylor_dyn_elementals {
($( $(#[$doc:meta])* $name:ident => $kernel:ident / $scratch:tt; )+) => {$(
$(#[$doc])*
#[inline]
pub fn $name(self) -> Self {
taylor_dyn_elementals!(@body $kernel, self, $scratch)
}
)+};
(@body $kernel:ident, $self:ident, 0) => {
Self::unary_op(&$self, |a, c| taylor_ops::$kernel(a, c))
};
(@body $kernel:ident, $self:ident, 1) => {
Self::unary_op_scratch(&$self, 1, |a, c, s| taylor_ops::$kernel(a, c, s))
};
(@body $kernel:ident, $self:ident, 2) => {
Self::unary_op_scratch(&$self, 2, |a, c, s| {
let (s1, s2) = s.split_at_mut(c.len());
taylor_ops::$kernel(a, c, s1, s2)
})
};
}
impl<F: Float + TaylorArenaLocal> TaylorDyn<F> {
#[inline]
pub fn variable(val: F) -> Self {
with_active_arena(|arena: &mut TaylorArena<F>| {
let idx = arena.allocate();
let coeffs = arena.coeffs_mut(idx);
coeffs[0] = val;
if coeffs.len() > 1 {
coeffs[1] = F::one();
}
TaylorDyn {
value: val,
index: idx,
}
})
}
#[inline]
pub fn from_coeffs(coeffs: &[F]) -> Self {
with_active_arena(|arena: &mut TaylorArena<F>| {
let idx = arena.allocate();
let slot = arena.coeffs_mut(idx);
let copy_len = coeffs.len().min(slot.len());
slot[..copy_len].copy_from_slice(&coeffs[..copy_len]);
TaylorDyn {
value: coeffs[0],
index: idx,
}
})
}
#[inline]
pub fn value(&self) -> F {
self.value
}
#[inline]
pub fn index(&self) -> u32 {
self.index
}
pub fn coeffs(&self) -> Vec<F> {
if self.index == CONSTANT {
with_active_arena(|arena: &mut TaylorArena<F>| {
let mut v = vec![F::zero(); arena.degree()];
v[0] = self.value;
v
})
} else {
with_active_arena(|arena: &mut TaylorArena<F>| arena.coeffs(self.index).to_vec())
}
}
pub fn derivative(&self, k: usize) -> F {
let ck = if k == 0 {
self.value
} else if self.index == CONSTANT {
F::zero()
} else {
with_active_arena(|arena: &mut TaylorArena<F>| arena.coeffs(self.index)[k])
};
let mut result = ck;
for i in 2..=k {
result = result * F::from(i).unwrap();
}
result
}
pub(crate) fn unary_op(x: &Self, f: impl FnOnce(&[F], &mut [F])) -> Self {
Self::unary_op_scratch(x, 0, |a, c, _| f(a, c))
}
pub(crate) fn unary_op_scratch(
x: &Self,
slabs: usize,
f: impl FnOnce(&[F], &mut [F], &mut [F]),
) -> Self {
with_active_arena(|arena: &mut TaylorArena<F>| {
let deg = arena.degree();
let idx = arena.allocate();
if x.index == CONSTANT {
let (_, slot, scratch) = arena.split_output(idx, slabs + 1);
let (fs, synth) = scratch.split_at_mut(slabs * deg);
synth[0] = x.value;
f(&*synth, slot, fs);
TaylorDyn {
value: slot[0],
index: idx,
}
} else {
let (before, slot, fs) = arena.split_output(idx, slabs);
let start = x.index as usize * deg;
f(&before[start..start + deg], slot, fs);
TaylorDyn {
value: slot[0],
index: idx,
}
}
})
}
pub(crate) fn binary_op(x: &Self, y: &Self, f: impl FnOnce(&[F], &[F], &mut [F])) -> Self {
if x.index == CONSTANT && y.index == CONSTANT {
let deg = with_active_arena(|arena: &mut TaylorArena<F>| arena.degree());
let mut a = vec![F::zero(); deg];
a[0] = x.value;
let mut b = vec![F::zero(); deg];
b[0] = y.value;
let mut result = vec![F::zero(); deg];
f(&a, &b, &mut result);
if result[1..].iter().all(|&c| c == F::zero()) {
return TaylorDyn {
value: result[0],
index: CONSTANT,
};
}
return with_active_arena(|arena: &mut TaylorArena<F>| {
let idx = arena.allocate();
let slot = arena.coeffs_mut(idx);
slot.copy_from_slice(&result);
TaylorDyn {
value: result[0],
index: idx,
}
});
}
Self::binary_op_scratch(x, y, 0, |a, b, c, _| f(a, b, c))
}
pub(crate) fn binary_op_scratch(
x: &Self,
y: &Self,
slabs: usize,
f: impl FnOnce(&[F], &[F], &mut [F], &mut [F]),
) -> Self {
with_active_arena(|arena: &mut TaylorArena<F>| {
let deg = arena.degree();
let idx = arena.allocate();
let const_slabs = usize::from(x.index == CONSTANT) + usize::from(y.index == CONSTANT);
let (before, slot, scratch) = arena.split_output(idx, slabs + const_slabs);
let (fs, synth) = scratch.split_at_mut(slabs * deg);
let mut synth_chunks = synth.chunks_mut(deg);
let a: &[F] = if x.index == CONSTANT {
let s = synth_chunks.next().expect("const slab reserved");
s[0] = x.value;
&*s
} else {
let start = x.index as usize * deg;
&before[start..start + deg]
};
let b: &[F] = if y.index == CONSTANT {
let s = synth_chunks.next().expect("const slab reserved");
s[0] = y.value;
&*s
} else {
let start = y.index as usize * deg;
&before[start..start + deg]
};
f(a, b, slot, fs);
TaylorDyn {
value: slot[0],
index: idx,
}
})
}
taylor_dyn_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 {
Self::unary_op_scratch(&self, 2, |a, c, s| {
let (s1, s2) = s.split_at_mut(c.len());
taylor_ops::taylor_powi(a, n, c, s1, s2);
})
}
#[inline]
pub fn powf(self, n: Self) -> Self {
let b = n.coeffs();
Self::unary_op(&self, |a, c| {
let deg = c.len();
let mut s1 = vec![F::zero(); deg];
let mut s2 = vec![F::zero(); deg];
taylor_ops::taylor_powf(a, &b, c, &mut s1, &mut s2);
})
}
#[inline]
pub fn log(self, base: Self) -> Self {
self.ln() / base.ln()
}
#[inline]
pub fn sin(self) -> Self {
Self::unary_op_scratch(&self, 1, |a, c, co| taylor_ops::taylor_sin_cos(a, c, co))
}
#[inline]
pub fn cos(self) -> Self {
Self::unary_op_scratch(&self, 1, |a, c, s| taylor_ops::taylor_sin_cos(a, s, c))
}
#[inline]
pub fn sin_cos(self) -> (Self, Self) {
let a = self.coeffs();
with_active_arena(|arena: &mut TaylorArena<F>| {
let deg = arena.degree();
let sin_idx = arena.allocate();
let cos_idx = arena.allocate();
let mut s = vec![F::zero(); deg];
let mut co = vec![F::zero(); deg];
taylor_ops::taylor_sin_cos(&a, &mut s, &mut co);
arena.coeffs_mut(sin_idx).copy_from_slice(&s);
arena.coeffs_mut(cos_idx).copy_from_slice(&co);
(
TaylorDyn {
value: s[0],
index: sin_idx,
},
TaylorDyn {
value: co[0],
index: cos_idx,
},
)
})
}
#[inline]
pub fn atan2(self, other: Self) -> Self {
let b = other.coeffs();
Self::unary_op(&self, |a, c| {
let n = c.len();
let mut s1 = vec![F::zero(); n];
let mut s2 = vec![F::zero(); n];
let mut s3 = vec![F::zero(); n];
taylor_ops::taylor_atan2(a, &b, c, &mut s1, &mut s2, &mut s3);
})
}
#[inline]
pub fn sinh(self) -> Self {
Self::unary_op_scratch(&self, 1, |a, c, ch| taylor_ops::taylor_sinh_cosh(a, c, ch))
}
#[inline]
pub fn cosh(self) -> Self {
Self::unary_op_scratch(&self, 1, |a, c, sh| taylor_ops::taylor_sinh_cosh(a, sh, c))
}
#[inline]
pub fn abs(self) -> Self {
Self::unary_op(&self, |a, c| {
let sign = if a[0] != F::zero() {
a[0].signum()
} else if let Some(k) = (1..a.len()).find(|&k| a[k] != F::zero()) {
a[k].signum()
} else {
F::one()
};
for k in 0..c.len() {
c[k] = a[k] * sign;
}
})
}
#[inline]
pub fn signum(self) -> Self {
TaylorDyn::constant(self.value.signum())
}
#[inline]
pub fn floor(self) -> Self {
TaylorDyn::constant(self.value.floor())
}
#[inline]
pub fn ceil(self) -> Self {
TaylorDyn::constant(self.value.ceil())
}
#[inline]
pub fn round(self) -> Self {
TaylorDyn::constant(self.value.round())
}
#[inline]
pub fn trunc(self) -> Self {
TaylorDyn::constant(self.value.trunc())
}
#[inline]
pub fn fract(self) -> Self {
Self::unary_op(&self, |a, c| {
c[0] = a[0].fract();
c[1..].copy_from_slice(&a[1..]);
})
}
#[inline]
pub fn hypot(self, other: Self) -> Self {
Self::binary_op_scratch(&self, &other, 2, |a, b, c, s| {
let (s1, s2) = s.split_at_mut(c.len());
taylor_ops::taylor_hypot(a, b, c, s1, s2);
})
}
#[inline]
pub fn max(self, other: Self) -> Self {
if self.value >= other.value || other.value.is_nan() {
self
} else {
other
}
}
#[inline]
pub fn min(self, other: Self) -> Self {
if self.value <= other.value || other.value.is_nan() {
self
} else {
other
}
}
}
impl<F: Float> Display for TaylorDyn<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
impl<F: Float> Default for TaylorDyn<F> {
fn default() -> Self {
TaylorDyn::constant(F::zero())
}
}