use std::any::TypeId;
use crate::{Array, Float, IntoArray, Num, simd_math};
#[derive(Clone, Copy)]
struct Kernels {
f64: Option<KernelPair<f64>>,
f32: Option<KernelPair<f32>>,
}
type KernelPair<T> = (fn(&[T], &mut [T]), fn(&mut [T]));
const EXP: Kernels = Kernels {
f64: Some((simd_math::exp_f64, simd_math::exp_f64_inplace)),
f32: Some((simd_math::exp_f32, simd_math::exp_f32_inplace)),
};
const EXP2: Kernels = Kernels {
f64: Some((simd_math::exp2_f64, simd_math::exp2_f64_inplace)),
f32: None,
};
const LOG: Kernels = Kernels {
f64: Some((simd_math::log_f64, simd_math::log_f64_inplace)),
f32: None,
};
const SIN: Kernels = Kernels {
f64: Some((simd_math::sin_f64, simd_math::sin_f64_inplace)),
f32: None,
};
const COS: Kernels = Kernels {
f64: Some((simd_math::cos_f64, simd_math::cos_f64_inplace)),
f32: None,
};
const TANH: Kernels = Kernels {
f64: Some((simd_math::tanh_f64, simd_math::tanh_f64_inplace)),
f32: None,
};
const SIGMOID: Kernels = Kernels {
f64: Some((simd_math::sigmoid_f64, simd_math::sigmoid_f64_inplace)),
f32: None,
};
#[cfg(feature = "rayon")]
const SIMD_CHUNK: usize = 1 << 14;
fn scalar_sigmoid<F: Float>(x: F) -> F {
if x >= F::ZERO {
F::ONE / (F::ONE + (-x).exp())
} else {
let e = x.exp();
e / (F::ONE + e)
}
}
unsafe fn same_type_slice<A, B>(s: &[A]) -> &[B] {
unsafe { std::slice::from_raw_parts(s.as_ptr().cast::<B>(), s.len()) }
}
unsafe fn same_type_slice_mut<A, B>(s: &mut [A]) -> &mut [B] {
unsafe { std::slice::from_raw_parts_mut(s.as_mut_ptr().cast::<B>(), s.len()) }
}
fn run_chunked<T: Sync + Send>(kernel: fn(&[T], &mut [T]), src: &[T], dst: &mut [T]) {
#[cfg(feature = "rayon")]
if src.len() >= crate::kernels_mem::UNARY {
use rayon::prelude::*;
dst.par_chunks_mut(SIMD_CHUNK)
.zip(src.par_chunks(SIMD_CHUNK))
.for_each(|(d, s)| kernel(s, d));
return;
}
kernel(src, dst);
}
fn run_chunked_inplace<T: Send>(kernel: fn(&mut [T]), data: &mut [T]) {
#[cfg(feature = "rayon")]
if data.len() >= crate::kernels_mem::UNARY {
use rayon::prelude::*;
data.par_chunks_mut(SIMD_CHUNK).for_each(kernel);
return;
}
kernel(data);
}
impl<F: Float> Array<F> {
fn simd_map(&self, kernels: Kernels, scalar: fn(F) -> F) -> Self {
let is64 = TypeId::of::<F>() == TypeId::of::<f64>();
let is32 = TypeId::of::<F>() == TypeId::of::<f32>();
if let Some(src) = self.as_slice() {
if is64 && let Some((kernel, _)) = kernels.f64 {
let mut out = crate::pool::output::<F>(src.len());
unsafe { run_chunked(kernel, same_type_slice(src), same_type_slice_mut(&mut out)) };
return Array::from_vec(out, self.shape());
}
if is32 && let Some((kernel, _)) = kernels.f32 {
let mut out = crate::pool::output::<F>(src.len());
unsafe { run_chunked(kernel, same_type_slice(src), same_type_slice_mut(&mut out)) };
return Array::from_vec(out, self.shape());
}
}
self.par_map(scalar)
}
fn simd_map_inplace(&mut self, kernels: Kernels, scalar: fn(F) -> F) {
let is64 = TypeId::of::<F>() == TypeId::of::<f64>();
let is32 = TypeId::of::<F>() == TypeId::of::<f32>();
let kernel64 = kernels.f64.filter(|_| is64).map(|(_, k)| k);
let kernel32 = kernels.f32.filter(|_| is32).map(|(_, k)| k);
if (kernel64.is_some() || kernel32.is_some())
&& let Some(data) = self.contiguous_mut()
{
if let Some(kernel) = kernel64 {
run_chunked_inplace(kernel, unsafe { same_type_slice_mut(data) });
} else if let Some(kernel) = kernel32 {
run_chunked_inplace(kernel, unsafe { same_type_slice_mut(data) });
}
return;
}
self.mapv_inplace_parallel(scalar)
}
}
impl<F: Float> Array<F> {
pub fn exp(&self) -> Self {
self.simd_map(EXP, F::exp)
}
pub fn exp2(&self) -> Self {
self.simd_map(EXP2, F::exp2)
}
pub fn expm1(&self) -> Self {
self.par_map(F::exp_m1)
}
pub fn log(&self) -> Self {
self.simd_map(LOG, F::ln)
}
pub fn log2(&self) -> Self {
self.par_map(F::log2)
}
pub fn log10(&self) -> Self {
self.par_map(F::log10)
}
pub fn log1p(&self) -> Self {
self.par_map(F::ln_1p)
}
pub fn sqrt(&self) -> Self {
self.par_map(F::sqrt)
}
pub fn cbrt(&self) -> Self {
self.par_map(F::cbrt)
}
pub fn sin(&self) -> Self {
self.simd_map(SIN, F::sin)
}
pub fn cos(&self) -> Self {
self.simd_map(COS, F::cos)
}
pub fn tan(&self) -> Self {
self.par_map(F::tan)
}
pub fn arcsin(&self) -> Self {
self.par_map(F::asin)
}
pub fn arccos(&self) -> Self {
self.par_map(F::acos)
}
pub fn arctan(&self) -> Self {
self.par_map(F::atan)
}
pub fn sinh(&self) -> Self {
self.par_map(F::sinh)
}
pub fn cosh(&self) -> Self {
self.par_map(F::cosh)
}
pub fn tanh(&self) -> Self {
self.simd_map(TANH, F::tanh)
}
pub fn floor(&self) -> Self {
self.par_map(F::floor)
}
pub fn ceil(&self) -> Self {
self.par_map(F::ceil)
}
pub fn round(&self) -> Self {
self.par_map(F::round)
}
pub fn round_decimals(&self, d: i32) -> Self {
let scale = F::from_usize(10).powf(F::from_f64((d as f64).abs()));
self.par_map(|x| {
if d >= 0 {
(x * scale).round() / scale
} else {
(x / scale).round() * scale
}
})
}
pub fn trunc(&self) -> Self {
self.par_map(F::trunc)
}
pub fn reciprocal(&self) -> Self {
self.par_map(|x| F::ONE / x)
}
pub fn powf(&self, p: F) -> Self {
self.par_map(|x| x.powf(p))
}
pub fn powi(&self, p: i32) -> Self {
self.par_map(|x| x.powi(p))
}
pub fn is_nan(&self) -> Array<bool> {
self.par_map(F::is_nan)
}
pub fn is_inf(&self) -> Array<bool> {
self.par_map(F::is_infinite)
}
pub fn is_finite(&self) -> Array<bool> {
self.par_map(F::is_finite)
}
pub fn sigmoid(&self) -> Self {
self.simd_map(SIGMOID, scalar_sigmoid)
}
pub fn relu(&self) -> Self {
self.par_map(|x| x.max(F::ZERO))
}
}
impl<T: Num> Array<T> {
pub fn abs(&self) -> Self {
self.par_map(Num::abs)
}
pub fn square(&self) -> Self {
self.par_map(|x| crate::element::mul(x, x))
}
pub fn clip(&self, lo: T, hi: T) -> Self {
self.par_map(|x| crate::element::minimum_value(crate::element::maximum_value(x, lo), hi))
}
pub fn sign(&self) -> Self {
self.par_map(|x| {
if x > T::ZERO {
T::ONE
} else if x < T::ZERO {
crate::element::sub(T::ZERO, T::ONE)
} else if crate::element::isnan(x) {
x
} else {
T::ZERO
}
})
}
}
pub fn maximum<T: Num>(a: impl IntoArray<T>, b: impl IntoArray<T>) -> Array<T> {
a.into_array().par_zip(b, crate::element::maximum_value)
}
pub fn minimum<T: Num>(a: impl IntoArray<T>, b: impl IntoArray<T>) -> Array<T> {
a.into_array().par_zip(b, crate::element::minimum_value)
}
pub fn power<T: Num>(a: impl IntoArray<T>, b: impl IntoArray<T>) -> Array<T> {
a.into_array().par_zip(b, |x, y| {
use std::any::Any;
if let (Some(&x), Some(&y)) = (
(&x as &dyn Any).downcast_ref::<f32>(),
(&y as &dyn Any).downcast_ref::<f32>(),
) {
let v = x.powf(y);
return *(&v as &dyn Any).downcast_ref::<T>().unwrap();
}
if let (Some(&x), Some(&y)) = (
(&x as &dyn Any).downcast_ref::<f64>(),
(&y as &dyn Any).downcast_ref::<f64>(),
) {
let v = x.powf(y);
return *(&v as &dyn Any).downcast_ref::<T>().unwrap();
}
assert!(
y >= T::ZERO,
"Integers to negative integer powers are not allowed."
);
let mut exponent = y;
let mut base = x;
let mut result = T::ONE;
let two = T::from_usize(2);
while exponent > T::ZERO {
if exponent % two != T::ZERO {
result = crate::element::mul(result, base)
}
exponent = exponent / two;
if exponent > T::ZERO {
base = crate::element::mul(base, base)
}
}
result
})
}
pub fn arctan2<F: Float>(a: impl IntoArray<F>, b: impl IntoArray<F>) -> Array<F> {
a.into_array().par_zip(b, F::atan2)
}
pub fn hypot<F: Float>(a: impl IntoArray<F>, b: impl IntoArray<F>) -> Array<F> {
a.into_array().par_zip(b, F::hypot)
}
pub fn where_<T: crate::Element>(
c: &Array<bool>,
x: impl IntoArray<T>,
y: impl IntoArray<T>,
) -> Array<T> {
let x = x.into_array();
let y = y.into_array();
let shape = crate::broadcast_shapes(&x.common_shape(&y), c.shape())
.expect("operands could not be broadcast together");
#[cfg(feature = "rayon")]
let parallel = c.is_contiguous() && x.is_contiguous() && y.is_contiguous();
let c = c.broadcast_to(&shape);
let x = x.broadcast_to(&shape);
let y = y.broadcast_to(&shape);
#[cfg(feature = "rayon")]
if parallel && crate::layout::size(&shape) >= crate::kernels_mem::UNARY {
use rayon::prelude::*;
return Array::from_vec(
(0..crate::layout::size(&shape))
.into_par_iter()
.map(|i| {
if c.flat_value(i) {
x.flat_value(i)
} else {
y.flat_value(i)
}
})
.collect(),
&shape,
);
}
Array::from_vec(
c.iter()
.zip(x.iter())
.zip(y.iter())
.map(|((c, x), y)| if c { x } else { y })
.collect(),
&shape,
)
}
impl<F: Float> Array<F> {
pub fn exp_inplace(&mut self) {
self.simd_map_inplace(EXP, F::exp)
}
pub fn into_exp(mut self) -> Self {
self.exp_inplace();
self
}
pub fn exp2_inplace(&mut self) {
self.simd_map_inplace(EXP2, F::exp2)
}
pub fn into_exp2(mut self) -> Self {
self.exp2_inplace();
self
}
pub fn expm1_inplace(&mut self) {
self.mapv_inplace_parallel(F::exp_m1)
}
pub fn into_expm1(mut self) -> Self {
self.expm1_inplace();
self
}
pub fn log_inplace(&mut self) {
self.simd_map_inplace(LOG, F::ln)
}
pub fn into_log(mut self) -> Self {
self.log_inplace();
self
}
pub fn log2_inplace(&mut self) {
self.mapv_inplace_parallel(F::log2)
}
pub fn into_log2(mut self) -> Self {
self.log2_inplace();
self
}
pub fn log10_inplace(&mut self) {
self.mapv_inplace_parallel(F::log10)
}
pub fn into_log10(mut self) -> Self {
self.log10_inplace();
self
}
pub fn log1p_inplace(&mut self) {
self.mapv_inplace_parallel(F::ln_1p)
}
pub fn into_log1p(mut self) -> Self {
self.log1p_inplace();
self
}
pub fn sqrt_inplace(&mut self) {
self.mapv_inplace_parallel(F::sqrt)
}
pub fn into_sqrt(mut self) -> Self {
self.sqrt_inplace();
self
}
pub fn cbrt_inplace(&mut self) {
self.mapv_inplace_parallel(F::cbrt)
}
pub fn into_cbrt(mut self) -> Self {
self.cbrt_inplace();
self
}
pub fn sin_inplace(&mut self) {
self.simd_map_inplace(SIN, F::sin)
}
pub fn into_sin(mut self) -> Self {
self.sin_inplace();
self
}
pub fn cos_inplace(&mut self) {
self.simd_map_inplace(COS, F::cos)
}
pub fn into_cos(mut self) -> Self {
self.cos_inplace();
self
}
pub fn tan_inplace(&mut self) {
self.mapv_inplace_parallel(F::tan)
}
pub fn into_tan(mut self) -> Self {
self.tan_inplace();
self
}
pub fn arcsin_inplace(&mut self) {
self.mapv_inplace_parallel(F::asin)
}
pub fn into_arcsin(mut self) -> Self {
self.arcsin_inplace();
self
}
pub fn arccos_inplace(&mut self) {
self.mapv_inplace_parallel(F::acos)
}
pub fn into_arccos(mut self) -> Self {
self.arccos_inplace();
self
}
pub fn arctan_inplace(&mut self) {
self.mapv_inplace_parallel(F::atan)
}
pub fn into_arctan(mut self) -> Self {
self.arctan_inplace();
self
}
pub fn sinh_inplace(&mut self) {
self.mapv_inplace_parallel(F::sinh)
}
pub fn into_sinh(mut self) -> Self {
self.sinh_inplace();
self
}
pub fn cosh_inplace(&mut self) {
self.mapv_inplace_parallel(F::cosh)
}
pub fn into_cosh(mut self) -> Self {
self.cosh_inplace();
self
}
pub fn tanh_inplace(&mut self) {
self.simd_map_inplace(TANH, F::tanh)
}
pub fn into_tanh(mut self) -> Self {
self.tanh_inplace();
self
}
pub fn floor_inplace(&mut self) {
self.mapv_inplace_parallel(F::floor)
}
pub fn into_floor(mut self) -> Self {
self.floor_inplace();
self
}
pub fn ceil_inplace(&mut self) {
self.mapv_inplace_parallel(F::ceil)
}
pub fn into_ceil(mut self) -> Self {
self.ceil_inplace();
self
}
pub fn round_inplace(&mut self) {
self.mapv_inplace_parallel(F::round)
}
pub fn into_round(mut self) -> Self {
self.round_inplace();
self
}
pub fn round_decimals_inplace(&mut self, d: i32) {
let scale = F::from_usize(10).powf(F::from_f64((d as f64).abs()));
self.mapv_inplace_parallel(|x| {
if d >= 0 {
(x * scale).round() / scale
} else {
(x / scale).round() * scale
}
})
}
pub fn into_round_decimals(mut self, d: i32) -> Self {
self.round_decimals_inplace(d);
self
}
pub fn trunc_inplace(&mut self) {
self.mapv_inplace_parallel(F::trunc)
}
pub fn into_trunc(mut self) -> Self {
self.trunc_inplace();
self
}
pub fn reciprocal_inplace(&mut self) {
self.mapv_inplace_parallel(|x| F::ONE / x)
}
pub fn into_reciprocal(mut self) -> Self {
self.reciprocal_inplace();
self
}
pub fn powf_inplace(&mut self, p: F) {
self.mapv_inplace_parallel(|x| x.powf(p))
}
pub fn into_powf(mut self, p: F) -> Self {
self.powf_inplace(p);
self
}
pub fn powi_inplace(&mut self, p: i32) {
self.mapv_inplace_parallel(|x| x.powi(p))
}
pub fn into_powi(mut self, p: i32) -> Self {
self.powi_inplace(p);
self
}
pub fn sigmoid_inplace(&mut self) {
self.simd_map_inplace(SIGMOID, scalar_sigmoid)
}
pub fn into_sigmoid(mut self) -> Self {
self.sigmoid_inplace();
self
}
pub fn relu_inplace(&mut self) {
self.mapv_inplace_parallel(|x| x.max(F::ZERO))
}
pub fn into_relu(mut self) -> Self {
self.relu_inplace();
self
}
}
impl<T: Num> Array<T> {
pub fn abs_inplace(&mut self) {
self.mapv_inplace_parallel(Num::abs)
}
pub fn into_abs(mut self) -> Self {
self.abs_inplace();
self
}
pub fn square_inplace(&mut self) {
self.mapv_inplace_parallel(|x| crate::element::mul(x, x))
}
pub fn into_square(mut self) -> Self {
self.square_inplace();
self
}
pub fn clip_inplace(&mut self, lo: T, hi: T) {
self.mapv_inplace_parallel(|x| {
crate::element::minimum_value(crate::element::maximum_value(x, lo), hi)
})
}
pub fn into_clip(mut self, lo: T, hi: T) -> Self {
self.clip_inplace(lo, hi);
self
}
pub fn sign_inplace(&mut self) {
self.mapv_inplace_parallel(|x| {
if x > T::ZERO {
T::ONE
} else if x < T::ZERO {
crate::element::sub(T::ZERO, T::ONE)
} else if crate::element::isnan(x) {
x
} else {
T::ZERO
}
})
}
pub fn into_sign(mut self) -> Self {
self.sign_inplace();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nan_propagation() {
assert!(maximum(f64::NAN, 1.).item().is_nan());
assert!(minimum(1., f64::NAN).item().is_nan());
assert!(crate::array![f64::NAN].sign().item().is_nan());
}
#[test]
fn ties_even_decimals() {
assert_eq!(
crate::array![1.25, 1.35, 2.5].round_decimals(1).to_vec(),
[1.2, 1.4, 2.5]
);
}
#[test]
fn stable_sigmoid() {
assert_eq!(
crate::array![-1000., 0., 1000.].sigmoid().to_vec(),
[0., 0.5, 1.]
);
}
}