use crate::application::array::Array;
use crate::application::reduction::iter_elements;
use crate::infrastructure::storage::{Storage, StorageMut, 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")
}
#[track_caller]
fn binary_elementwise_in_place<T, Sa, Sb, F, const N: usize>(
lhs: &mut Array<T, Sa, N>,
rhs: &Array<T, Sb, N>,
op: F,
) where
T: Copy,
Sa: StorageMut<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 rhs_view = rhs.view();
if let Some(out) = lhs.as_slice_mut() {
if let Some(r_slice) = rhs_view.as_slice() {
for (slot, &b) in out.iter_mut().zip(r_slice) {
*slot = op(*slot, b);
}
} else {
for (slot, b) in out.iter_mut().zip(iter_elements(&rhs_view)) {
*slot = op(*slot, *b);
}
}
return;
}
let slots = lhs
.try_iter_mut()
.expect("invariant: an owned operator lhs addresses each element exactly once");
for (slot, b) in slots.zip(iter_elements(&rhs_view)) {
*slot = op(*slot, *b);
}
}
#[track_caller]
fn unary_elementwise_in_place<T, Sa, F, const N: usize>(arr: &mut Array<T, Sa, N>, op: F)
where
T: Copy,
Sa: StorageMut<T>,
F: Fn(T) -> T,
{
if let Some(out) = arr.as_slice_memory_order_mut() {
for slot in out.iter_mut() {
*slot = op(*slot);
}
return;
}
let slots = arr
.try_iter_mut()
.expect("invariant: an owned operator operand addresses each element exactly once");
for slot in slots {
*slot = op(*slot);
}
}
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<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: StorageMut<T>,
Sb: Storage<T>,
{
type Output = Array<T, Sa, N>;
#[inline]
#[track_caller]
fn $method(mut self, rhs: &Array<T, Sb, N>) -> Self::Output {
binary_elementwise_in_place(&mut self, rhs, |a, b| {
core::ops::$trait::$method(a, b)
});
self
}
}
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: StorageMut<T>,
Sb: Storage<T>,
{
type Output = Array<T, Sa, N>;
#[inline]
#[track_caller]
fn $method(self, rhs: Array<T, Sb, N>) -> Self::Output {
core::ops::$trait::$method(self, &rhs)
}
}
impl<T, Sa, const N: usize> core::ops::$trait<T> for Array<T, Sa, N>
where
T: ScalarOperand + core::ops::$trait<Output = T>,
Sa: StorageMut<T>,
{
type Output = Array<T, Sa, N>;
#[inline]
#[track_caller]
fn $method(mut self, rhs: T) -> Self::Output {
unary_elementwise_in_place(&mut self, move |a| core::ops::$trait::$method(a, rhs));
self
}
}
};
}
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)
}
}
impl<T, Sa, const N: usize> core::ops::Neg for Array<T, Sa, N>
where
T: Copy + core::ops::Neg<Output = T>,
Sa: StorageMut<T>,
{
type Output = Array<T, Sa, N>;
#[inline]
#[track_caller]
fn neg(mut self) -> Self::Output {
unary_elementwise_in_place(&mut self, |a| -a);
self
}
}