use crate::application::array::Array;
use crate::application::index::index_from_flat;
use crate::application::iter::AxisIter;
use crate::application::reduction::iter_elements;
use crate::domain::error::{LetoError, Result};
use crate::domain::layout::Layout;
use crate::domain::remove_axis::{RankMarker, RemoveAxis};
use crate::infrastructure::storage::{Storage, VecStorage};
pub fn min_all<T, S, const N: usize>(arr: &Array<T, S, N>) -> Result<T>
where
T: PartialOrd + Copy,
S: Storage<T>,
{
if arr.size() == 0 {
return Err(LetoError::StorageError {
reason: "reduction over empty array is undefined".to_string(),
});
}
let view = arr.view();
let mut it = iter_elements(&view);
let mut acc = *it.next().expect("non-empty array has at least one element");
for elem in it {
if *elem < acc {
acc = *elem;
}
}
Ok(acc)
}
pub fn max_all<T, S, const N: usize>(arr: &Array<T, S, N>) -> Result<T>
where
T: PartialOrd + Copy,
S: Storage<T>,
{
if arr.size() == 0 {
return Err(LetoError::StorageError {
reason: "reduction over empty array is undefined".to_string(),
});
}
let view = arr.view();
let mut it = iter_elements(&view);
let mut acc = *it.next().expect("non-empty array has at least one element");
for elem in it {
if *elem > acc {
acc = *elem;
}
}
Ok(acc)
}
pub fn argmin_all<T, S, const N: usize>(arr: &Array<T, S, N>) -> Result<[usize; N]>
where
T: PartialOrd + Copy,
S: Storage<T>,
{
arg_reduce_all(arr, |best, candidate| candidate < best)
}
pub fn argmax_all<T, S, const N: usize>(arr: &Array<T, S, N>) -> Result<[usize; N]>
where
T: PartialOrd + Copy,
S: Storage<T>,
{
arg_reduce_all(arr, |best, candidate| candidate > best)
}
pub fn min_axis<T, S, const N: usize, const M: usize>(
arr: &Array<T, S, N>,
axis: usize,
) -> Result<Array<T, VecStorage<T>, M>>
where
T: PartialOrd + Copy,
S: Storage<T>,
RankMarker<N>: RemoveAxis<N, SmallerShape = [usize; M], SmallerStrides = [isize; M]>,
{
axis_reduce_lanes::<T, S, N, M, _>(arr, axis, |acc, elem| if elem < acc { elem } else { acc })
}
pub fn max_axis<T, S, const N: usize, const M: usize>(
arr: &Array<T, S, N>,
axis: usize,
) -> Result<Array<T, VecStorage<T>, M>>
where
T: PartialOrd + Copy,
S: Storage<T>,
RankMarker<N>: RemoveAxis<N, SmallerShape = [usize; M], SmallerStrides = [isize; M]>,
{
axis_reduce_lanes::<T, S, N, M, _>(arr, axis, |acc, elem| if elem > acc { elem } else { acc })
}
pub fn argmin<T, S, const N: usize, const M: usize>(
arr: &Array<T, S, N>,
axis: usize,
) -> Result<Array<usize, VecStorage<usize>, M>>
where
T: PartialOrd + Copy,
S: Storage<T>,
RankMarker<N>: RemoveAxis<N, SmallerShape = [usize; M], SmallerStrides = [isize; M]>,
{
axis_arg_reduce::<T, S, N, M>(arr, axis, |a, b| b < a)
}
pub fn argmax<T, S, const N: usize, const M: usize>(
arr: &Array<T, S, N>,
axis: usize,
) -> Result<Array<usize, VecStorage<usize>, M>>
where
T: PartialOrd + Copy,
S: Storage<T>,
RankMarker<N>: RemoveAxis<N, SmallerShape = [usize; M], SmallerStrides = [isize; M]>,
{
axis_arg_reduce::<T, S, N, M>(arr, axis, |a, b| b > a)
}
fn arg_reduce_all<T, S, const N: usize>(
arr: &Array<T, S, N>,
is_better: impl Fn(T, T) -> bool,
) -> Result<[usize; N]>
where
T: Copy,
S: Storage<T>,
{
if arr.size() == 0 {
return Err(LetoError::StorageError {
reason: "argreduce over empty array is undefined".to_string(),
});
}
let view = arr.view();
let shape = view.layout().shape;
let mut it = iter_elements(&view).enumerate();
let (_, first) = it.next().expect("non-empty array has at least one element");
let mut best_val = *first;
let mut best_flat = 0usize;
for (flat, elem) in it {
if is_better(best_val, *elem) {
best_val = *elem;
best_flat = flat;
}
}
Ok(index_from_flat(best_flat, &shape))
}
fn axis_reduce_lanes<T, S, const N: usize, const M: usize, F>(
arr: &Array<T, S, N>,
axis: usize,
fold: F,
) -> Result<Array<T, VecStorage<T>, M>>
where
T: Copy,
S: Storage<T>,
F: Fn(T, T) -> T,
RankMarker<N>: RemoveAxis<N, SmallerShape = [usize; M], SmallerStrides = [isize; M]>,
{
if axis >= N {
return Err(LetoError::StorageError {
reason: format!("axis {axis} out of bounds for rank {N}"),
});
}
let axis_len = arr.shape()[axis];
if axis_len == 0 {
return Err(LetoError::StorageError {
reason: format!("axis {axis} has length 0"),
});
}
let out_shape = RankMarker::<N>.remove_shape(arr.shape(), axis)?;
let out_size: usize = out_shape.iter().product();
let view = arr.view();
let mut iter: AxisIter<'_, T, N, M> = AxisIter::new(&view, axis, RankMarker::<N>)?;
let first = iter.next().expect("axis_len > 0 implies at least one lane");
let mut buf: Vec<T> = Vec::with_capacity(out_size);
if let Some(slice) = first.as_slice() {
buf.extend_from_slice(slice);
} else {
for val in first.iter() {
buf.push(*val);
}
}
for lane in iter {
if let Some(slice) = lane.as_slice() {
for (buf_val, &lane_val) in buf.iter_mut().zip(slice) {
*buf_val = fold(*buf_val, lane_val);
}
} else {
for (flat, val) in lane.iter().enumerate() {
buf[flat] = fold(buf[flat], *val);
}
}
}
let out_layout = Layout::c_contiguous(out_shape)?;
let storage = VecStorage::new(buf);
Array::new(out_layout, storage)
}
fn axis_arg_reduce<T, S, const N: usize, const M: usize>(
arr: &Array<T, S, N>,
axis: usize,
is_better: impl Fn(T, T) -> bool,
) -> Result<Array<usize, VecStorage<usize>, M>>
where
T: Copy,
S: Storage<T>,
RankMarker<N>: RemoveAxis<N, SmallerShape = [usize; M], SmallerStrides = [isize; M]>,
{
if axis >= N {
return Err(LetoError::StorageError {
reason: format!("axis {axis} out of bounds for rank {N}"),
});
}
let axis_len = arr.shape()[axis];
if axis_len == 0 {
return Err(LetoError::StorageError {
reason: format!("axis {axis} has length 0"),
});
}
let out_shape = RankMarker::<N>.remove_shape(arr.shape(), axis)?;
let out_size: usize = out_shape.iter().product();
let view = arr.view();
let mut iter: AxisIter<'_, T, N, M> = AxisIter::new(&view, axis, RankMarker::<N>)?;
let first = iter.next().expect("axis_len > 0 implies at least one lane");
let mut best_val: Vec<T> = Vec::with_capacity(out_size);
if let Some(slice) = first.as_slice() {
best_val.extend_from_slice(slice);
} else {
for val in first.iter() {
best_val.push(*val);
}
}
let mut best_idx: Vec<usize> = vec![0usize; out_size];
for (lane_pos, lane) in iter.enumerate() {
let lane_axis_pos = lane_pos + 1;
if let Some(slice) = lane.as_slice() {
for ((val_ref, idx_ref), &candidate) in
best_val.iter_mut().zip(&mut best_idx).zip(slice)
{
if is_better(*val_ref, candidate) {
*val_ref = candidate;
*idx_ref = lane_axis_pos;
}
}
} else {
for ((val_ref, idx_ref), val) in best_val.iter_mut().zip(&mut best_idx).zip(lane.iter())
{
let candidate = *val;
if is_better(*val_ref, candidate) {
*val_ref = candidate;
*idx_ref = lane_axis_pos;
}
}
}
}
let out_layout = Layout::c_contiguous(out_shape)?;
let storage = VecStorage::new(best_idx);
Array::new(out_layout, storage)
}