use std::{borrow::Cow, ops::Bound, rc::Rc};
use mlx_internal_macros::generate_macro;
use crate::{
error::{Exception, Result},
utils::guard::Guarded,
Array, Stream,
};
pub(crate) mod index_impl;
pub(crate) mod indexmut_impl;
mod indexupdate_impl;
#[derive(Debug, Clone, Copy)]
pub struct NewAxis;
#[derive(Debug, Clone, Copy)]
pub struct Ellipsis;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum UpdateMode {
#[default]
Replace,
Add,
Min,
Max,
Product,
}
#[derive(Debug, thiserror::Error)]
pub enum IndexUpdateError {
#[error("index update stride for axis {axis} must not be zero")]
ZeroStride {
axis: usize,
},
#[error(transparent)]
Exception(#[from] Exception),
}
#[derive(Debug, Clone, Copy)]
pub struct StrideBy<I> {
pub inner: I,
pub stride: i32,
}
pub trait IntoStrideBy: Sized {
fn stride_by(self, stride: i32) -> StrideBy<Self>;
}
impl<T> IntoStrideBy for T {
fn stride_by(self, stride: i32) -> StrideBy<Self> {
StrideBy {
inner: self,
stride,
}
}
}
#[derive(Debug, Clone)]
pub struct RangeIndex {
start: Bound<i32>,
stop: Bound<i32>,
stride: i32,
}
impl RangeIndex {
pub(crate) fn new(start: Bound<i32>, stop: Bound<i32>, stride: Option<i32>) -> Self {
let stride = stride.unwrap_or(1);
Self {
start,
stop,
stride,
}
}
pub(crate) fn is_full(&self) -> bool {
matches!(self.start, Bound::Unbounded)
&& matches!(self.stop, Bound::Unbounded)
&& self.stride == 1
}
pub(crate) fn stride(&self) -> i32 {
self.stride
}
pub(crate) fn start(&self, size: i32) -> i32 {
match self.start {
Bound::Included(start) => start,
Bound::Excluded(start) => start + 1,
Bound::Unbounded => {
if self.stride.is_negative() {
size - 1
} else {
0
}
}
}
}
pub(crate) fn absolute_start(&self, size: i32) -> i32 {
let start = self.start(size);
if start.is_negative() {
start + size
} else {
start
}
}
pub(crate) fn end(&self, size: i32) -> i32 {
match self.stop {
Bound::Included(stop) => stop + 1,
Bound::Excluded(stop) => stop,
Bound::Unbounded => {
if self.stride.is_negative() {
-size - 1
} else {
size
}
}
}
}
pub(crate) fn absolute_end(&self, size: i32) -> i32 {
let end = self.end(size);
if end.is_negative() {
end + size
} else {
end
}
}
}
#[derive(Debug, Clone)]
pub enum ArrayIndexOp<'a> {
Ellipsis,
TakeIndex {
index: i32,
},
TakeArray {
indices: Rc<Array>, },
TakeArrayRef {
indices: &'a Array,
},
Slice(RangeIndex),
ExpandDims,
}
impl ArrayIndexOp<'_> {
fn is_array_or_index(&self) -> bool {
match self {
ArrayIndexOp::TakeIndex { .. }
| ArrayIndexOp::TakeArrayRef { .. }
| ArrayIndexOp::TakeArray { .. } => true,
ArrayIndexOp::Ellipsis | ArrayIndexOp::Slice(_) | ArrayIndexOp::ExpandDims => false,
}
}
fn is_array(&self) -> bool {
match self {
ArrayIndexOp::TakeArray { .. } | ArrayIndexOp::TakeArrayRef { .. } => true,
ArrayIndexOp::TakeIndex { .. }
| ArrayIndexOp::Ellipsis
| ArrayIndexOp::Slice(_)
| ArrayIndexOp::ExpandDims => false,
}
}
}
pub trait TryIndexOp<Idx> {
fn try_index_device(&self, i: Idx, stream: impl AsRef<Stream>) -> Result<Array>;
fn try_index(&self, i: Idx) -> Result<Array> {
self.try_index_device(i, Stream::thread_local_or_default())
}
}
pub trait IndexOp<Idx>: TryIndexOp<Idx> {
fn index_device(&self, i: Idx, stream: impl AsRef<Stream>) -> Array {
self.try_index_device(i, stream).unwrap()
}
fn index(&self, i: Idx) -> Array {
self.try_index(i).unwrap()
}
}
impl<T, Idx> IndexOp<Idx> for T where T: TryIndexOp<Idx> {}
pub trait TryIndexMutOp<Idx, Val> {
fn try_index_mut_device(&mut self, i: Idx, val: Val, stream: impl AsRef<Stream>) -> Result<()>;
fn try_index_mut(&mut self, i: Idx, val: Val) -> Result<()> {
self.try_index_mut_device(i, val, Stream::thread_local_or_default())
}
}
pub trait TryIndexUpdateOp<Idx, Value> {
fn try_index_update(
&self,
index: Idx,
update: Value,
mode: UpdateMode,
) -> std::result::Result<Array, IndexUpdateError>;
}
pub trait IndexMutOp<Idx, Val>: TryIndexMutOp<Idx, Val> {
fn index_mut_device(&mut self, i: Idx, val: Val, stream: impl AsRef<Stream>) {
self.try_index_mut_device(i, val, stream).unwrap()
}
fn index_mut(&mut self, i: Idx, val: Val) {
self.try_index_mut(i, val).unwrap()
}
}
impl<T, Idx, Val> IndexMutOp<Idx, Val> for T where T: TryIndexMutOp<Idx, Val> {}
pub trait ArrayIndex<'a> {
fn index_op(self) -> ArrayIndexOp<'a>;
}
impl Array {
pub fn take_axis(&self, indices: impl AsRef<Array>, axis: i32) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_take_axis(
res,
self.as_ptr(),
indices.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `take_axis`"
)]
pub fn take_axis_device(
&self,
indices: impl AsRef<Array>,
axis: i32,
stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || self.take_axis(indices, axis))
}
pub fn take(&self, indices: impl AsRef<Array>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_take(
res,
self.as_ptr(),
indices.as_ref().as_ptr(),
stream.as_ref().as_ptr(),
)
})
}
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `take`"
)]
pub fn take_device(
&self,
indices: impl AsRef<Array>,
stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || self.take(indices))
}
pub fn take_along_axis(
&self,
indices: impl AsRef<Array>,
axis: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let (input, axis) = match axis.into() {
None => (Cow::Owned(self.reshape(&[-1])?), 0),
Some(ax) => (Cow::Borrowed(self), ax),
};
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_take_along_axis(
res,
input.as_ptr(),
indices.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `take_along_axis`"
)]
pub fn take_along_axis_device(
&self,
indices: impl AsRef<Array>,
axis: impl Into<Option<i32>>,
stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || self.take_along_axis(indices, axis))
}
pub fn put_along_axis(
&self,
indices: impl AsRef<Array>,
values: impl AsRef<Array>,
axis: impl Into<Option<i32>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
match axis.into() {
None => {
let input = self.reshape(&[-1])?;
let array = Array::try_from_op(|res| unsafe {
mlx_sys::mlx_put_along_axis(
res,
input.as_ptr(),
indices.as_ref().as_ptr(),
values.as_ref().as_ptr(),
0,
stream.as_ref().as_ptr(),
)
})?;
let array = array.reshape(self.shape())?;
Ok(array)
}
Some(ax) => Array::try_from_op(|res| unsafe {
mlx_sys::mlx_put_along_axis(
res,
self.as_ptr(),
indices.as_ref().as_ptr(),
values.as_ref().as_ptr(),
ax,
stream.as_ref().as_ptr(),
)
}),
}
}
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `put_along_axis`"
)]
pub fn put_along_axis_device(
&self,
indices: impl AsRef<Array>,
values: impl AsRef<Array>,
axis: impl Into<Option<i32>>,
stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
self.put_along_axis(indices, values, axis)
})
}
}
pub fn argmax_axis(
a: impl AsRef<Array>,
axis: i32,
keep_dims: impl Into<Option<bool>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let keep_dims = keep_dims.into().unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_argmax_axis(
res,
a.as_ref().as_ptr(),
axis,
keep_dims,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `argmax_axis`"
)]
pub fn argmax_axis_device(
a: impl AsRef<Array>,
axis: i32,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || argmax_axis(a, axis, keep_dims))
}
pub fn argmax(a: impl AsRef<Array>, keep_dims: impl Into<Option<bool>>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let keep_dims = keep_dims.into().unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_argmax(
res,
a.as_ref().as_ptr(),
keep_dims,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `argmax`"
)]
pub fn argmax_device(
a: impl AsRef<Array>,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || argmax(a, keep_dims))
}
pub fn argmin_axis(
a: impl AsRef<Array>,
axis: i32,
keep_dims: impl Into<Option<bool>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let keep_dims = keep_dims.into().unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_argmin_axis(
res,
a.as_ref().as_ptr(),
axis,
keep_dims,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `argmin_axis`"
)]
pub fn argmin_axis_device(
a: impl AsRef<Array>,
axis: i32,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || argmin_axis(a, axis, keep_dims))
}
pub fn argmin(a: impl AsRef<Array>, keep_dims: impl Into<Option<bool>>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let keep_dims = keep_dims.into().unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_argmin(
res,
a.as_ref().as_ptr(),
keep_dims,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `argmin`"
)]
pub fn argmin_device(
a: impl AsRef<Array>,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || argmin(a, keep_dims))
}
pub fn take_along_axis(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
axis: impl Into<Option<i32>>,
) -> Result<Array> {
a.as_ref().take_along_axis(indices, axis)
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `take_along_axis`"
)]
pub fn take_along_axis_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
#[optional] axis: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || take_along_axis(a, indices, axis))
}
pub fn put_along_axis(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
values: impl AsRef<Array>,
axis: impl Into<Option<i32>>,
) -> Result<Array> {
a.as_ref().put_along_axis(indices, values, axis)
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `put_along_axis`"
)]
pub fn put_along_axis_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
values: impl AsRef<Array>,
#[optional] axis: impl Into<Option<i32>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || put_along_axis(a, indices, values, axis))
}
pub fn take_axis(a: impl AsRef<Array>, indices: impl AsRef<Array>, axis: i32) -> Result<Array> {
a.as_ref().take_axis(indices, axis)
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `take_axis`"
)]
pub fn take_axis_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || take_axis(a, indices, axis))
}
pub fn take(a: impl AsRef<Array>, indices: impl AsRef<Array>) -> Result<Array> {
a.as_ref().take(indices)
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `take`"
)]
pub fn take_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || take(a, indices))
}
pub fn topk_axis(a: impl AsRef<Array>, k: i32, axis: i32) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_topk_axis(res, a.as_ref().as_ptr(), k, axis, stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `topk_axis`"
)]
pub fn topk_axis_device(
a: impl AsRef<Array>,
k: i32,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || topk_axis(a, k, axis))
}
pub fn topk(a: impl AsRef<Array>, k: i32) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_topk(res, a.as_ref().as_ptr(), k, stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `topk`"
)]
pub fn topk_device(
a: impl AsRef<Array>,
k: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || topk(a, k))
}
pub fn scatter_single(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_scatter_single(
res,
a.as_ref().as_ptr(),
indices.as_ref().as_ptr(),
updates.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `scatter_single`"
)]
pub fn scatter_single_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
scatter_single(a, indices, updates, axis)
})
}
pub fn scatter_add_single(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_scatter_add_single(
res,
a.as_ref().as_ptr(),
indices.as_ref().as_ptr(),
updates.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `scatter_add_single`"
)]
pub fn scatter_add_single_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
scatter_add_single(a, indices, updates, axis)
})
}
pub fn scatter_max_single(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_scatter_max_single(
res,
a.as_ref().as_ptr(),
indices.as_ref().as_ptr(),
updates.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `scatter_max_single`"
)]
pub fn scatter_max_single_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
scatter_max_single(a, indices, updates, axis)
})
}
pub fn scatter_min_single(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_scatter_min_single(
res,
a.as_ref().as_ptr(),
indices.as_ref().as_ptr(),
updates.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `scatter_min_single`"
)]
pub fn scatter_min_single_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
scatter_min_single(a, indices, updates, axis)
})
}
pub fn scatter_prod_single(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_scatter_prod_single(
res,
a.as_ref().as_ptr(),
indices.as_ref().as_ptr(),
updates.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `scatter_prod_single`"
)]
pub fn scatter_prod_single_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
updates: impl AsRef<Array>,
axis: i32,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
scatter_prod_single(a, indices, updates, axis)
})
}
pub fn gather_single(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
axis: i32,
slice_sizes: &[i32],
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_gather_single(
res,
a.as_ref().as_ptr(),
indices.as_ref().as_ptr(),
axis,
slice_sizes.as_ptr(),
slice_sizes.len(),
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `gather_single`"
)]
pub fn gather_single_device(
a: impl AsRef<Array>,
indices: impl AsRef<Array>,
axis: i32,
slice_sizes: &[i32],
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || {
gather_single(a, indices, axis, slice_sizes)
})
}
pub fn masked_scatter(
a: impl AsRef<Array>,
mask: impl AsRef<Array>,
src: impl AsRef<Array>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_masked_scatter(
res,
a.as_ref().as_ptr(),
mask.as_ref().as_ptr(),
src.as_ref().as_ptr(),
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `masked_scatter`"
)]
pub fn masked_scatter_device(
a: impl AsRef<Array>,
mask: impl AsRef<Array>,
src: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || masked_scatter(a, mask, src))
}
fn count_non_new_axis_operations(operations: &[ArrayIndexOp]) -> usize {
operations
.iter()
.filter(|op| !matches!(op, ArrayIndexOp::ExpandDims))
.count()
}
fn expand_ellipsis_operations<'a>(
ndim: usize,
operations: &'a [ArrayIndexOp<'a>],
) -> Cow<'a, [ArrayIndexOp<'a>]> {
let ellipsis_count = operations
.iter()
.filter(|op| matches!(op, ArrayIndexOp::Ellipsis))
.count();
if ellipsis_count == 0 {
return Cow::Borrowed(operations);
}
if ellipsis_count > 1 {
panic!("Indexing with multiple ellipsis is not supported");
}
let ellipsis_pos = operations
.iter()
.position(|op| matches!(op, ArrayIndexOp::Ellipsis))
.unwrap();
let prefix = &operations[..ellipsis_pos];
let suffix = &operations[(ellipsis_pos + 1)..];
let expand_range =
count_non_new_axis_operations(prefix)..(ndim - count_non_new_axis_operations(suffix));
let expand = expand_range.map(|_| (..).index_op());
let mut expanded = Vec::with_capacity(ndim);
expanded.extend_from_slice(prefix);
expanded.extend(expand);
expanded.extend_from_slice(suffix);
Cow::Owned(expanded)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{array, ops::reshape, Array};
struct StreamCheckedArray {
array: Array,
expected: Stream,
}
impl TryIndexOp<i32> for StreamCheckedArray {
fn try_index_device(&self, i: i32, stream: impl AsRef<Stream>) -> Result<Array> {
assert_eq!(stream.as_ref(), &self.expected);
self.array.try_index_device(i, stream)
}
}
impl TryIndexMutOp<i32, Array> for StreamCheckedArray {
fn try_index_mut_device(
&mut self,
i: i32,
val: Array,
stream: impl AsRef<Stream>,
) -> Result<()> {
assert_eq!(stream.as_ref(), &self.expected);
self.array.try_index_mut_device(i, val, stream)
}
}
#[test]
fn indexed_read_uses_scoped_stream() {
let stream = Stream::new_with_device(&crate::Device::cpu());
assert_ne!(stream, Stream::default());
crate::with_stream(&stream, || {
let input = StreamCheckedArray {
array: array!([1, 2, 3]),
expected: stream.clone(),
};
assert_eq!(input.try_index(1).unwrap().item_exact::<i32>(), 2);
});
}
#[test]
fn indexed_write_uses_scoped_stream() {
let stream = Stream::new_with_device(&crate::Device::cpu());
assert_ne!(stream, Stream::default());
crate::with_stream(&stream, || {
let mut input = StreamCheckedArray {
array: array!([1, 2, 3]),
expected: stream.clone(),
};
input.try_index_mut(1, array!(7)).unwrap();
assert_eq!(input.array.as_slice::<i32>(), &[1, 7, 3]);
});
}
#[test]
fn test_scatter_single() {
let input = Array::zeros::<f32>(&[4]).unwrap();
let indices = Array::from_slice(&[0u32, 1], &[2]);
let updates = Array::ones::<f32>(&[2, 1]).unwrap();
let out = scatter_single(&input, &indices, &updates, 0).unwrap();
let expected = array!([1.0f32, 1.0, 0.0, 0.0]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
#[test]
fn test_scatter_add_single() {
let input = Array::ones::<f32>(&[4]).unwrap();
let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
let updates = Array::ones::<f32>(&[3, 1]).unwrap();
let out = scatter_add_single(&input, &indices, &updates, 0).unwrap();
let expected = array!([3.0f32, 1.0, 1.0, 2.0]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
#[test]
fn test_scatter_max_single() {
let input = Array::ones::<f32>(&[4]).unwrap();
let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
let updates = reshape(array!([1.0f32, 6.0, -2.0]), &[3, 1]).unwrap();
let out = scatter_max_single(&input, &indices, &updates, 0).unwrap();
let expected = array!([6.0f32, 1.0, 1.0, 1.0]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
#[test]
fn test_scatter_min_single() {
let input = Array::ones::<f32>(&[4]).unwrap();
let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
let updates = reshape(array!([1.0f32, -6.0, 2.0]), &[3, 1]).unwrap();
let out = scatter_min_single(&input, &indices, &updates, 0).unwrap();
let expected = array!([-6.0f32, 1.0, 1.0, 1.0]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
#[test]
fn test_scatter_prod_single() {
let input = Array::ones::<f32>(&[4]).unwrap();
let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
let updates = Array::full::<f32>(&[3, 1], array!(2.0f32)).unwrap();
let out = scatter_prod_single(&input, &indices, &updates, 0).unwrap();
let expected = array!([4.0f32, 1.0, 1.0, 2.0]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
#[test]
fn test_gather_single() {
let input = Array::from_slice(&[0.0f32, 1.0, 2.0, 3.0], &[4]);
let indices = Array::from_slice(&[1u32, 3], &[2]);
let out = gather_single(&input, &indices, 0, &[1]).unwrap();
let expected = array!([[1.0f32], [3.0]]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
#[test]
fn test_masked_scatter() {
let input = Array::from_slice(&[1.0f32, 2.0, 3.0, 4.0], &[4]);
let mask = Array::from_slice(&[true, false, true, false], &[4]);
let src = Array::from_slice(&[10.0f32, 20.0], &[2]);
let out = masked_scatter(&input, &mask, &src).unwrap();
let expected = array!([10.0f32, 2.0, 20.0, 4.0]);
assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
}
}