use crate::application::array::Array;
use crate::application::reduction::iter_elements;
use crate::infrastructure::storage::{Storage, VecStorage};
use eunomia::{Bf16, F16};
mod sealed {
pub trait Sealed {}
}
pub trait ScalarOperand: sealed::Sealed + Copy {}
macro_rules! impl_scalar_operand {
($($t:ty),* $(,)?) => {
$(
impl sealed::Sealed for $t {}
impl ScalarOperand for $t {}
)*
};
}
impl_scalar_operand!(
f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, F16, Bf16
);
#[track_caller]
fn binary_elementwise<T, Sa, Sb, F, const N: usize>(
lhs: &Array<T, Sa, N>,
rhs: &Array<T, Sb, N>,
op: F,
) -> Array<T, VecStorage<T>, N>
where
T: Copy,
Sa: Storage<T>,
Sb: Storage<T>,
F: Fn(T, T) -> T,
{
let shape = lhs.shape();
assert_eq!(
shape,
rhs.shape(),
"elementwise operator requires equal shapes: lhs {:?} != rhs {:?}",
shape,
rhs.shape()
);
let lhs_view = lhs.view();
let rhs_view = rhs.view();
let values: Vec<T> =
if let (Some(l_slice), Some(r_slice)) = (lhs_view.as_slice(), rhs_view.as_slice()) {
l_slice
.iter()
.zip(r_slice)
.map(|(&a, &b)| op(a, b))
.collect()
} else {
iter_elements(&lhs_view)
.zip(iter_elements(&rhs_view))
.map(|(a, b)| op(*a, *b))
.collect()
};
Array::from_shape_vec(shape, values)
.expect("invariant: C-contiguous shape matches the produced element count")
}
fn unary_elementwise<T, Sa, F, const N: usize>(
arr: &Array<T, Sa, N>,
op: F,
) -> Array<T, VecStorage<T>, N>
where
T: Copy,
Sa: Storage<T>,
F: Fn(T) -> T,
{
let shape = arr.shape();
let view = arr.view();
let values: Vec<T> = if let Some(slice) = view.as_slice() {
slice.iter().map(|&a| op(a)).collect()
} else {
iter_elements(&view).map(|a| op(*a)).collect()
};
Array::from_shape_vec(shape, values)
.expect("invariant: C-contiguous shape matches the produced element count")
}
macro_rules! impl_array_operator {
($trait:ident, $method:ident) => {
impl<T, Sa, Sb, const N: usize> core::ops::$trait<&Array<T, Sb, N>> for &Array<T, Sa, N>
where
T: Copy + core::ops::$trait<Output = T>,
Sa: Storage<T>,
Sb: Storage<T>,
{
type Output = Array<T, VecStorage<T>, N>;
#[inline]
fn $method(self, rhs: &Array<T, Sb, N>) -> Self::Output {
binary_elementwise(self, rhs, |a, b| core::ops::$trait::$method(a, b))
}
}
impl<T, Sa, const N: usize> core::ops::$trait<T> for &Array<T, Sa, N>
where
T: ScalarOperand + core::ops::$trait<Output = T>,
Sa: Storage<T>,
{
type Output = Array<T, VecStorage<T>, N>;
#[inline]
fn $method(self, rhs: T) -> Self::Output {
unary_elementwise(self, move |a| core::ops::$trait::$method(a, rhs))
}
}
};
}
impl_array_operator!(Add, add);
impl_array_operator!(Sub, sub);
impl_array_operator!(Mul, mul);
impl_array_operator!(Div, div);
impl<T, Sa, const N: usize> core::ops::Neg for &Array<T, Sa, N>
where
T: Copy + core::ops::Neg<Output = T>,
Sa: Storage<T>,
{
type Output = Array<T, VecStorage<T>, N>;
#[inline]
fn neg(self) -> Self::Output {
unary_elementwise(self, |a| -a)
}
}