1use std::{borrow::Cow, ops::Bound, rc::Rc};
100
101use mlx_internal_macros::generate_macro;
102
103use crate::{
104 error::{Exception, Result},
105 utils::guard::Guarded,
106 Array, Stream, StreamOrDevice,
107};
108
109pub(crate) mod index_impl;
110pub(crate) mod indexmut_impl;
111mod indexupdate_impl;
112
113#[derive(Debug, Clone, Copy)]
121pub struct NewAxis;
122
123#[derive(Debug, Clone, Copy)]
127pub struct Ellipsis;
128
129#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
131pub enum UpdateMode {
132 #[default]
134 Replace,
135 Add,
137 Min,
139 Max,
141 Product,
143}
144
145#[derive(Debug, thiserror::Error)]
147pub enum IndexUpdateError {
148 #[error("index update stride for axis {axis} must not be zero")]
150 ZeroStride {
151 axis: usize,
153 },
154
155 #[error(transparent)]
157 Exception(#[from] Exception),
158}
159
160#[derive(Debug, Clone, Copy)]
164pub struct StrideBy<I> {
165 pub inner: I,
167
168 pub stride: i32,
170}
171
172pub trait IntoStrideBy: Sized {
174 fn stride_by(self, stride: i32) -> StrideBy<Self>;
176}
177
178impl<T> IntoStrideBy for T {
179 fn stride_by(self, stride: i32) -> StrideBy<Self> {
180 StrideBy {
181 inner: self,
182 stride,
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
189pub struct RangeIndex {
190 start: Bound<i32>,
191 stop: Bound<i32>,
192 stride: i32,
193}
194
195impl RangeIndex {
196 pub(crate) fn new(start: Bound<i32>, stop: Bound<i32>, stride: Option<i32>) -> Self {
197 let stride = stride.unwrap_or(1);
198 Self {
199 start,
200 stop,
201 stride,
202 }
203 }
204
205 pub(crate) fn is_full(&self) -> bool {
206 matches!(self.start, Bound::Unbounded)
207 && matches!(self.stop, Bound::Unbounded)
208 && self.stride == 1
209 }
210
211 pub(crate) fn stride(&self) -> i32 {
212 self.stride
213 }
214
215 pub(crate) fn start(&self, size: i32) -> i32 {
216 match self.start {
217 Bound::Included(start) => start,
218 Bound::Excluded(start) => start + 1,
219 Bound::Unbounded => {
220 if self.stride.is_negative() {
224 size - 1
225 } else {
226 0
227 }
228 }
229 }
230 }
231
232 pub(crate) fn absolute_start(&self, size: i32) -> i32 {
233 let start = self.start(size);
237 if start.is_negative() {
238 start + size
239 } else {
240 start
241 }
242 }
243
244 pub(crate) fn end(&self, size: i32) -> i32 {
245 match self.stop {
246 Bound::Included(stop) => stop + 1,
247 Bound::Excluded(stop) => stop,
248 Bound::Unbounded => {
249 if self.stride.is_negative() {
253 -size - 1
254 } else {
255 size
256 }
257 }
258 }
259 }
260
261 pub(crate) fn absolute_end(&self, size: i32) -> i32 {
262 let end = self.end(size);
266 if end.is_negative() {
267 end + size
268 } else {
269 end
270 }
271 }
272}
273
274#[derive(Debug, Clone)]
276pub enum ArrayIndexOp<'a> {
277 Ellipsis,
281
282 TakeIndex {
286 index: i32,
288 },
289
290 TakeArray {
292 indices: Rc<Array>, },
295
296 TakeArrayRef {
298 indices: &'a Array,
300 },
301
302 Slice(RangeIndex),
306
307 ExpandDims,
311}
312
313impl ArrayIndexOp<'_> {
314 fn is_array_or_index(&self) -> bool {
315 match self {
317 ArrayIndexOp::TakeIndex { .. }
318 | ArrayIndexOp::TakeArrayRef { .. }
319 | ArrayIndexOp::TakeArray { .. } => true,
320 ArrayIndexOp::Ellipsis | ArrayIndexOp::Slice(_) | ArrayIndexOp::ExpandDims => false,
321 }
322 }
323 fn is_array(&self) -> bool {
324 match self {
326 ArrayIndexOp::TakeArray { .. } | ArrayIndexOp::TakeArrayRef { .. } => true,
327 ArrayIndexOp::TakeIndex { .. }
328 | ArrayIndexOp::Ellipsis
329 | ArrayIndexOp::Slice(_)
330 | ArrayIndexOp::ExpandDims => false,
331 }
332 }
333}
334
335pub trait TryIndexOp<Idx> {
343 fn try_index_device(&self, i: Idx, stream: impl AsRef<Stream>) -> Result<Array>;
345
346 fn try_index(&self, i: Idx) -> Result<Array> {
348 self.try_index_device(i, StreamOrDevice::default())
349 }
350}
351
352pub trait IndexOp<Idx>: TryIndexOp<Idx> {
356 fn index_device(&self, i: Idx, stream: impl AsRef<Stream>) -> Array {
358 self.try_index_device(i, stream).unwrap()
359 }
360
361 fn index(&self, i: Idx) -> Array {
363 self.try_index(i).unwrap()
364 }
365}
366
367impl<T, Idx> IndexOp<Idx> for T where T: TryIndexOp<Idx> {}
368
369pub trait TryIndexMutOp<Idx, Val> {
371 fn try_index_mut_device(&mut self, i: Idx, val: Val, stream: impl AsRef<Stream>) -> Result<()>;
373
374 fn try_index_mut(&mut self, i: Idx, val: Val) -> Result<()> {
376 self.try_index_mut_device(i, val, StreamOrDevice::default())
377 }
378}
379
380pub trait TryIndexUpdateOp<Idx, Value> {
388 fn try_index_update(
390 &self,
391 index: Idx,
392 update: Value,
393 mode: UpdateMode,
394 ) -> std::result::Result<Array, IndexUpdateError>;
395}
396pub trait IndexMutOp<Idx, Val>: TryIndexMutOp<Idx, Val> {
400 fn index_mut_device(&mut self, i: Idx, val: Val, stream: impl AsRef<Stream>) {
402 self.try_index_mut_device(i, val, stream).unwrap()
403 }
404
405 fn index_mut(&mut self, i: Idx, val: Val) {
407 self.try_index_mut(i, val).unwrap()
408 }
409}
410
411impl<T, Idx, Val> IndexMutOp<Idx, Val> for T where T: TryIndexMutOp<Idx, Val> {}
412
413pub trait ArrayIndex<'a> {
415 fn index_op(self) -> ArrayIndexOp<'a>;
417}
418
419impl Array {
425 pub fn take_axis(&self, indices: impl AsRef<Array>, axis: i32) -> Result<Array> {
437 let stream = Stream::thread_local_or_default();
438 Array::try_from_op(|res| unsafe {
439 mlx_sys::mlx_take_axis(
440 res,
441 self.as_ptr(),
442 indices.as_ref().as_ptr(),
443 axis,
444 stream.as_ref().as_ptr(),
445 )
446 })
447 }
448
449 #[deprecated(
451 since = "0.26.0",
452 note = "use `with_stream` or `with_device` around `take_axis`"
453 )]
454 pub fn take_axis_device(
455 &self,
456 indices: impl AsRef<Array>,
457 axis: i32,
458 stream: impl AsRef<Stream>,
459 ) -> Result<Array> {
460 crate::with_stream(stream.as_ref(), || self.take_axis(indices, axis))
461 }
462
463 pub fn take(&self, indices: impl AsRef<Array>) -> Result<Array> {
469 let stream = Stream::thread_local_or_default();
470 Array::try_from_op(|res| unsafe {
471 mlx_sys::mlx_take(
472 res,
473 self.as_ptr(),
474 indices.as_ref().as_ptr(),
475 stream.as_ref().as_ptr(),
476 )
477 })
478 }
479
480 #[deprecated(
482 since = "0.26.0",
483 note = "use `with_stream` or `with_device` around `take`"
484 )]
485 pub fn take_device(
486 &self,
487 indices: impl AsRef<Array>,
488 stream: impl AsRef<Stream>,
489 ) -> Result<Array> {
490 crate::with_stream(stream.as_ref(), || self.take(indices))
491 }
492
493 pub fn take_along_axis(
502 &self,
503 indices: impl AsRef<Array>,
504 axis: impl Into<Option<i32>>,
505 ) -> Result<Array> {
506 let stream = Stream::thread_local_or_default();
507 let (input, axis) = match axis.into() {
508 None => (Cow::Owned(self.reshape(&[-1])?), 0),
509 Some(ax) => (Cow::Borrowed(self), ax),
510 };
511
512 Array::try_from_op(|res| unsafe {
513 mlx_sys::mlx_take_along_axis(
514 res,
515 input.as_ptr(),
516 indices.as_ref().as_ptr(),
517 axis,
518 stream.as_ref().as_ptr(),
519 )
520 })
521 }
522
523 #[deprecated(
525 since = "0.26.0",
526 note = "use `with_stream` or `with_device` around `take_along_axis`"
527 )]
528 pub fn take_along_axis_device(
529 &self,
530 indices: impl AsRef<Array>,
531 axis: impl Into<Option<i32>>,
532 stream: impl AsRef<Stream>,
533 ) -> Result<Array> {
534 crate::with_stream(stream.as_ref(), || self.take_along_axis(indices, axis))
535 }
536
537 pub fn put_along_axis(
547 &self,
548 indices: impl AsRef<Array>,
549 values: impl AsRef<Array>,
550 axis: impl Into<Option<i32>>,
551 ) -> Result<Array> {
552 let stream = Stream::thread_local_or_default();
553 match axis.into() {
554 None => {
555 let input = self.reshape(&[-1])?;
556 let array = Array::try_from_op(|res| unsafe {
557 mlx_sys::mlx_put_along_axis(
558 res,
559 input.as_ptr(),
560 indices.as_ref().as_ptr(),
561 values.as_ref().as_ptr(),
562 0,
563 stream.as_ref().as_ptr(),
564 )
565 })?;
566 let array = array.reshape(self.shape())?;
567 Ok(array)
568 }
569 Some(ax) => Array::try_from_op(|res| unsafe {
570 mlx_sys::mlx_put_along_axis(
571 res,
572 self.as_ptr(),
573 indices.as_ref().as_ptr(),
574 values.as_ref().as_ptr(),
575 ax,
576 stream.as_ref().as_ptr(),
577 )
578 }),
579 }
580 }
581
582 #[deprecated(
584 since = "0.26.0",
585 note = "use `with_stream` or `with_device` around `put_along_axis`"
586 )]
587 pub fn put_along_axis_device(
588 &self,
589 indices: impl AsRef<Array>,
590 values: impl AsRef<Array>,
591 axis: impl Into<Option<i32>>,
592 stream: impl AsRef<Stream>,
593 ) -> Result<Array> {
594 crate::with_stream(stream.as_ref(), || {
595 self.put_along_axis(indices, values, axis)
596 })
597 }
598}
599
600pub fn argmax_axis(
610 a: impl AsRef<Array>,
611 axis: i32,
612 keep_dims: impl Into<Option<bool>>,
613) -> Result<Array> {
614 let stream = Stream::thread_local_or_default();
615 let keep_dims = keep_dims.into().unwrap_or(false);
616
617 Array::try_from_op(|res| unsafe {
618 mlx_sys::mlx_argmax_axis(
619 res,
620 a.as_ref().as_ptr(),
621 axis,
622 keep_dims,
623 stream.as_ref().as_ptr(),
624 )
625 })
626}
627
628#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
630#[deprecated(
631 since = "0.26.0",
632 note = "use `with_stream` or `with_device` around `argmax_axis`"
633)]
634pub fn argmax_axis_device(
635 a: impl AsRef<Array>,
636 axis: i32,
637 #[optional] keep_dims: impl Into<Option<bool>>,
638 #[optional] stream: impl AsRef<Stream>,
639) -> Result<Array> {
640 crate::with_stream(stream.as_ref(), || argmax_axis(a, axis, keep_dims))
641}
642
643pub fn argmax(a: impl AsRef<Array>, keep_dims: impl Into<Option<bool>>) -> Result<Array> {
650 let stream = Stream::thread_local_or_default();
651 let keep_dims = keep_dims.into().unwrap_or(false);
652
653 Array::try_from_op(|res| unsafe {
654 mlx_sys::mlx_argmax(
655 res,
656 a.as_ref().as_ptr(),
657 keep_dims,
658 stream.as_ref().as_ptr(),
659 )
660 })
661}
662
663#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
665#[deprecated(
666 since = "0.26.0",
667 note = "use `with_stream` or `with_device` around `argmax`"
668)]
669pub fn argmax_device(
670 a: impl AsRef<Array>,
671 #[optional] keep_dims: impl Into<Option<bool>>,
672 #[optional] stream: impl AsRef<Stream>,
673) -> Result<Array> {
674 crate::with_stream(stream.as_ref(), || argmax(a, keep_dims))
675}
676
677pub fn argmin_axis(
687 a: impl AsRef<Array>,
688 axis: i32,
689 keep_dims: impl Into<Option<bool>>,
690) -> Result<Array> {
691 let stream = Stream::thread_local_or_default();
692 let keep_dims = keep_dims.into().unwrap_or(false);
693
694 Array::try_from_op(|res| unsafe {
695 mlx_sys::mlx_argmin_axis(
696 res,
697 a.as_ref().as_ptr(),
698 axis,
699 keep_dims,
700 stream.as_ref().as_ptr(),
701 )
702 })
703}
704
705#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
707#[deprecated(
708 since = "0.26.0",
709 note = "use `with_stream` or `with_device` around `argmin_axis`"
710)]
711pub fn argmin_axis_device(
712 a: impl AsRef<Array>,
713 axis: i32,
714 #[optional] keep_dims: impl Into<Option<bool>>,
715 #[optional] stream: impl AsRef<Stream>,
716) -> Result<Array> {
717 crate::with_stream(stream.as_ref(), || argmin_axis(a, axis, keep_dims))
718}
719
720pub fn argmin(a: impl AsRef<Array>, keep_dims: impl Into<Option<bool>>) -> Result<Array> {
727 let stream = Stream::thread_local_or_default();
728 let keep_dims = keep_dims.into().unwrap_or(false);
729
730 Array::try_from_op(|res| unsafe {
731 mlx_sys::mlx_argmin(
732 res,
733 a.as_ref().as_ptr(),
734 keep_dims,
735 stream.as_ref().as_ptr(),
736 )
737 })
738}
739
740#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
742#[deprecated(
743 since = "0.26.0",
744 note = "use `with_stream` or `with_device` around `argmin`"
745)]
746pub fn argmin_device(
747 a: impl AsRef<Array>,
748 #[optional] keep_dims: impl Into<Option<bool>>,
749 #[optional] stream: impl AsRef<Stream>,
750) -> Result<Array> {
751 crate::with_stream(stream.as_ref(), || argmin(a, keep_dims))
752}
753
754pub fn take_along_axis(
756 a: impl AsRef<Array>,
757 indices: impl AsRef<Array>,
758 axis: impl Into<Option<i32>>,
759) -> Result<Array> {
760 a.as_ref().take_along_axis(indices, axis)
761}
762
763#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
765#[deprecated(
766 since = "0.26.0",
767 note = "use `with_stream` or `with_device` around `take_along_axis`"
768)]
769pub fn take_along_axis_device(
770 a: impl AsRef<Array>,
771 indices: impl AsRef<Array>,
772 #[optional] axis: impl Into<Option<i32>>,
773 #[optional] stream: impl AsRef<Stream>,
774) -> Result<Array> {
775 crate::with_stream(stream.as_ref(), || take_along_axis(a, indices, axis))
776}
777
778pub fn put_along_axis(
780 a: impl AsRef<Array>,
781 indices: impl AsRef<Array>,
782 values: impl AsRef<Array>,
783 axis: impl Into<Option<i32>>,
784) -> Result<Array> {
785 a.as_ref().put_along_axis(indices, values, axis)
786}
787
788#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
790#[deprecated(
791 since = "0.26.0",
792 note = "use `with_stream` or `with_device` around `put_along_axis`"
793)]
794pub fn put_along_axis_device(
795 a: impl AsRef<Array>,
796 indices: impl AsRef<Array>,
797 values: impl AsRef<Array>,
798 #[optional] axis: impl Into<Option<i32>>,
799 #[optional] stream: impl AsRef<Stream>,
800) -> Result<Array> {
801 crate::with_stream(stream.as_ref(), || put_along_axis(a, indices, values, axis))
802}
803
804pub fn take_axis(a: impl AsRef<Array>, indices: impl AsRef<Array>, axis: i32) -> Result<Array> {
806 a.as_ref().take_axis(indices, axis)
807}
808
809#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
811#[deprecated(
812 since = "0.26.0",
813 note = "use `with_stream` or `with_device` around `take_axis`"
814)]
815pub fn take_axis_device(
816 a: impl AsRef<Array>,
817 indices: impl AsRef<Array>,
818 axis: i32,
819 #[optional] stream: impl AsRef<Stream>,
820) -> Result<Array> {
821 crate::with_stream(stream.as_ref(), || take_axis(a, indices, axis))
822}
823
824pub fn take(a: impl AsRef<Array>, indices: impl AsRef<Array>) -> Result<Array> {
826 a.as_ref().take(indices)
827}
828
829#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
831#[deprecated(
832 since = "0.26.0",
833 note = "use `with_stream` or `with_device` around `take`"
834)]
835pub fn take_device(
836 a: impl AsRef<Array>,
837 indices: impl AsRef<Array>,
838 #[optional] stream: impl AsRef<Stream>,
839) -> Result<Array> {
840 crate::with_stream(stream.as_ref(), || take(a, indices))
841}
842
843pub fn topk_axis(a: impl AsRef<Array>, k: i32, axis: i32) -> Result<Array> {
855 let stream = Stream::thread_local_or_default();
856 Array::try_from_op(|res| unsafe {
857 mlx_sys::mlx_topk_axis(res, a.as_ref().as_ptr(), k, axis, stream.as_ref().as_ptr())
858 })
859}
860
861#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
863#[deprecated(
864 since = "0.26.0",
865 note = "use `with_stream` or `with_device` around `topk_axis`"
866)]
867pub fn topk_axis_device(
868 a: impl AsRef<Array>,
869 k: i32,
870 axis: i32,
871 #[optional] stream: impl AsRef<Stream>,
872) -> Result<Array> {
873 crate::with_stream(stream.as_ref(), || topk_axis(a, k, axis))
874}
875
876pub fn topk(a: impl AsRef<Array>, k: i32) -> Result<Array> {
878 let stream = Stream::thread_local_or_default();
879 Array::try_from_op(|res| unsafe {
880 mlx_sys::mlx_topk(res, a.as_ref().as_ptr(), k, stream.as_ref().as_ptr())
881 })
882}
883
884#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
886#[deprecated(
887 since = "0.26.0",
888 note = "use `with_stream` or `with_device` around `topk`"
889)]
890pub fn topk_device(
891 a: impl AsRef<Array>,
892 k: i32,
893 #[optional] stream: impl AsRef<Stream>,
894) -> Result<Array> {
895 crate::with_stream(stream.as_ref(), || topk(a, k))
896}
897
898pub fn scatter_single(
907 a: impl AsRef<Array>,
908 indices: impl AsRef<Array>,
909 updates: impl AsRef<Array>,
910 axis: i32,
911) -> Result<Array> {
912 let stream = Stream::thread_local_or_default();
913 Array::try_from_op(|res| unsafe {
914 mlx_sys::mlx_scatter_single(
915 res,
916 a.as_ref().as_ptr(),
917 indices.as_ref().as_ptr(),
918 updates.as_ref().as_ptr(),
919 axis,
920 stream.as_ref().as_ptr(),
921 )
922 })
923}
924
925#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
927#[deprecated(
928 since = "0.26.0",
929 note = "use `with_stream` or `with_device` around `scatter_single`"
930)]
931pub fn scatter_single_device(
932 a: impl AsRef<Array>,
933 indices: impl AsRef<Array>,
934 updates: impl AsRef<Array>,
935 axis: i32,
936 #[optional] stream: impl AsRef<Stream>,
937) -> Result<Array> {
938 crate::with_stream(stream.as_ref(), || {
939 scatter_single(a, indices, updates, axis)
940 })
941}
942
943pub fn scatter_add_single(
954 a: impl AsRef<Array>,
955 indices: impl AsRef<Array>,
956 updates: impl AsRef<Array>,
957 axis: i32,
958) -> Result<Array> {
959 let stream = Stream::thread_local_or_default();
960 Array::try_from_op(|res| unsafe {
961 mlx_sys::mlx_scatter_add_single(
962 res,
963 a.as_ref().as_ptr(),
964 indices.as_ref().as_ptr(),
965 updates.as_ref().as_ptr(),
966 axis,
967 stream.as_ref().as_ptr(),
968 )
969 })
970}
971
972#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
974#[deprecated(
975 since = "0.26.0",
976 note = "use `with_stream` or `with_device` around `scatter_add_single`"
977)]
978pub fn scatter_add_single_device(
979 a: impl AsRef<Array>,
980 indices: impl AsRef<Array>,
981 updates: impl AsRef<Array>,
982 axis: i32,
983 #[optional] stream: impl AsRef<Stream>,
984) -> Result<Array> {
985 crate::with_stream(stream.as_ref(), || {
986 scatter_add_single(a, indices, updates, axis)
987 })
988}
989
990pub fn scatter_max_single(
1001 a: impl AsRef<Array>,
1002 indices: impl AsRef<Array>,
1003 updates: impl AsRef<Array>,
1004 axis: i32,
1005) -> Result<Array> {
1006 let stream = Stream::thread_local_or_default();
1007 Array::try_from_op(|res| unsafe {
1008 mlx_sys::mlx_scatter_max_single(
1009 res,
1010 a.as_ref().as_ptr(),
1011 indices.as_ref().as_ptr(),
1012 updates.as_ref().as_ptr(),
1013 axis,
1014 stream.as_ref().as_ptr(),
1015 )
1016 })
1017}
1018
1019#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
1021#[deprecated(
1022 since = "0.26.0",
1023 note = "use `with_stream` or `with_device` around `scatter_max_single`"
1024)]
1025pub fn scatter_max_single_device(
1026 a: impl AsRef<Array>,
1027 indices: impl AsRef<Array>,
1028 updates: impl AsRef<Array>,
1029 axis: i32,
1030 #[optional] stream: impl AsRef<Stream>,
1031) -> Result<Array> {
1032 crate::with_stream(stream.as_ref(), || {
1033 scatter_max_single(a, indices, updates, axis)
1034 })
1035}
1036
1037pub fn scatter_min_single(
1048 a: impl AsRef<Array>,
1049 indices: impl AsRef<Array>,
1050 updates: impl AsRef<Array>,
1051 axis: i32,
1052) -> Result<Array> {
1053 let stream = Stream::thread_local_or_default();
1054 Array::try_from_op(|res| unsafe {
1055 mlx_sys::mlx_scatter_min_single(
1056 res,
1057 a.as_ref().as_ptr(),
1058 indices.as_ref().as_ptr(),
1059 updates.as_ref().as_ptr(),
1060 axis,
1061 stream.as_ref().as_ptr(),
1062 )
1063 })
1064}
1065
1066#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
1068#[deprecated(
1069 since = "0.26.0",
1070 note = "use `with_stream` or `with_device` around `scatter_min_single`"
1071)]
1072pub fn scatter_min_single_device(
1073 a: impl AsRef<Array>,
1074 indices: impl AsRef<Array>,
1075 updates: impl AsRef<Array>,
1076 axis: i32,
1077 #[optional] stream: impl AsRef<Stream>,
1078) -> Result<Array> {
1079 crate::with_stream(stream.as_ref(), || {
1080 scatter_min_single(a, indices, updates, axis)
1081 })
1082}
1083
1084pub fn scatter_prod_single(
1095 a: impl AsRef<Array>,
1096 indices: impl AsRef<Array>,
1097 updates: impl AsRef<Array>,
1098 axis: i32,
1099) -> Result<Array> {
1100 let stream = Stream::thread_local_or_default();
1101 Array::try_from_op(|res| unsafe {
1102 mlx_sys::mlx_scatter_prod_single(
1103 res,
1104 a.as_ref().as_ptr(),
1105 indices.as_ref().as_ptr(),
1106 updates.as_ref().as_ptr(),
1107 axis,
1108 stream.as_ref().as_ptr(),
1109 )
1110 })
1111}
1112
1113#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
1115#[deprecated(
1116 since = "0.26.0",
1117 note = "use `with_stream` or `with_device` around `scatter_prod_single`"
1118)]
1119pub fn scatter_prod_single_device(
1120 a: impl AsRef<Array>,
1121 indices: impl AsRef<Array>,
1122 updates: impl AsRef<Array>,
1123 axis: i32,
1124 #[optional] stream: impl AsRef<Stream>,
1125) -> Result<Array> {
1126 crate::with_stream(stream.as_ref(), || {
1127 scatter_prod_single(a, indices, updates, axis)
1128 })
1129}
1130
1131pub fn gather_single(
1140 a: impl AsRef<Array>,
1141 indices: impl AsRef<Array>,
1142 axis: i32,
1143 slice_sizes: &[i32],
1144) -> Result<Array> {
1145 let stream = Stream::thread_local_or_default();
1146 Array::try_from_op(|res| unsafe {
1147 mlx_sys::mlx_gather_single(
1148 res,
1149 a.as_ref().as_ptr(),
1150 indices.as_ref().as_ptr(),
1151 axis,
1152 slice_sizes.as_ptr(),
1153 slice_sizes.len(),
1154 stream.as_ref().as_ptr(),
1155 )
1156 })
1157}
1158
1159#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
1161#[deprecated(
1162 since = "0.26.0",
1163 note = "use `with_stream` or `with_device` around `gather_single`"
1164)]
1165pub fn gather_single_device(
1166 a: impl AsRef<Array>,
1167 indices: impl AsRef<Array>,
1168 axis: i32,
1169 slice_sizes: &[i32],
1170 #[optional] stream: impl AsRef<Stream>,
1171) -> Result<Array> {
1172 crate::with_stream(stream.as_ref(), || {
1173 gather_single(a, indices, axis, slice_sizes)
1174 })
1175}
1176
1177pub fn masked_scatter(
1185 a: impl AsRef<Array>,
1186 mask: impl AsRef<Array>,
1187 src: impl AsRef<Array>,
1188) -> Result<Array> {
1189 let stream = Stream::thread_local_or_default();
1190 Array::try_from_op(|res| unsafe {
1191 mlx_sys::mlx_masked_scatter(
1192 res,
1193 a.as_ref().as_ptr(),
1194 mask.as_ref().as_ptr(),
1195 src.as_ref().as_ptr(),
1196 stream.as_ref().as_ptr(),
1197 )
1198 })
1199}
1200
1201#[generate_macro(customize(forwarding_shim = true, root = "$crate::ops::indexing"))]
1203#[deprecated(
1204 since = "0.26.0",
1205 note = "use `with_stream` or `with_device` around `masked_scatter`"
1206)]
1207pub fn masked_scatter_device(
1208 a: impl AsRef<Array>,
1209 mask: impl AsRef<Array>,
1210 src: impl AsRef<Array>,
1211 #[optional] stream: impl AsRef<Stream>,
1212) -> Result<Array> {
1213 crate::with_stream(stream.as_ref(), || masked_scatter(a, mask, src))
1214}
1215
1216fn count_non_new_axis_operations(operations: &[ArrayIndexOp]) -> usize {
1220 operations
1221 .iter()
1222 .filter(|op| !matches!(op, ArrayIndexOp::ExpandDims))
1223 .count()
1224}
1225fn expand_ellipsis_operations<'a>(
1226 ndim: usize,
1227 operations: &'a [ArrayIndexOp<'a>],
1228) -> Cow<'a, [ArrayIndexOp<'a>]> {
1229 let ellipsis_count = operations
1230 .iter()
1231 .filter(|op| matches!(op, ArrayIndexOp::Ellipsis))
1232 .count();
1233 if ellipsis_count == 0 {
1234 return Cow::Borrowed(operations);
1235 }
1236
1237 if ellipsis_count > 1 {
1238 panic!("Indexing with multiple ellipsis is not supported");
1239 }
1240
1241 let ellipsis_pos = operations
1242 .iter()
1243 .position(|op| matches!(op, ArrayIndexOp::Ellipsis))
1244 .unwrap();
1245 let prefix = &operations[..ellipsis_pos];
1246 let suffix = &operations[(ellipsis_pos + 1)..];
1247 let expand_range =
1248 count_non_new_axis_operations(prefix)..(ndim - count_non_new_axis_operations(suffix));
1249 let expand = expand_range.map(|_| (..).index_op());
1250
1251 let mut expanded = Vec::with_capacity(ndim);
1252 expanded.extend_from_slice(prefix);
1253 expanded.extend(expand);
1254 expanded.extend_from_slice(suffix);
1255
1256 Cow::Owned(expanded)
1257}
1258
1259#[cfg(test)]
1260mod tests {
1261 use super::*;
1262 use crate::{array, ops::reshape, Array};
1263
1264 #[test]
1266 fn test_scatter_single() {
1267 let input = Array::zeros::<f32>(&[4]).unwrap();
1269 let indices = Array::from_slice(&[0u32, 1], &[2]);
1270 let updates = Array::ones::<f32>(&[2, 1]).unwrap();
1271 let out = scatter_single(&input, &indices, &updates, 0).unwrap();
1272 let expected = array!([1.0f32, 1.0, 0.0, 0.0]);
1273 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1274 }
1275
1276 #[test]
1277 fn test_scatter_add_single() {
1278 let input = Array::ones::<f32>(&[4]).unwrap();
1280 let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
1281 let updates = Array::ones::<f32>(&[3, 1]).unwrap();
1282 let out = scatter_add_single(&input, &indices, &updates, 0).unwrap();
1283 let expected = array!([3.0f32, 1.0, 1.0, 2.0]);
1284 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1285 }
1286
1287 #[test]
1288 fn test_scatter_max_single() {
1289 let input = Array::ones::<f32>(&[4]).unwrap();
1291 let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
1292 let updates = reshape(array!([1.0f32, 6.0, -2.0]), &[3, 1]).unwrap();
1293 let out = scatter_max_single(&input, &indices, &updates, 0).unwrap();
1294 let expected = array!([6.0f32, 1.0, 1.0, 1.0]);
1295 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1296 }
1297
1298 #[test]
1299 fn test_scatter_min_single() {
1300 let input = Array::ones::<f32>(&[4]).unwrap();
1302 let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
1303 let updates = reshape(array!([1.0f32, -6.0, 2.0]), &[3, 1]).unwrap();
1304 let out = scatter_min_single(&input, &indices, &updates, 0).unwrap();
1305 let expected = array!([-6.0f32, 1.0, 1.0, 1.0]);
1306 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1307 }
1308
1309 #[test]
1310 fn test_scatter_prod_single() {
1311 let input = Array::ones::<f32>(&[4]).unwrap();
1313 let indices = Array::from_slice(&[0u32, 0, 3], &[3]);
1314 let updates = Array::full::<f32>(&[3, 1], array!(2.0f32)).unwrap();
1315 let out = scatter_prod_single(&input, &indices, &updates, 0).unwrap();
1316 let expected = array!([4.0f32, 1.0, 1.0, 2.0]);
1317 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1318 }
1319
1320 #[test]
1321 fn test_gather_single() {
1322 let input = Array::from_slice(&[0.0f32, 1.0, 2.0, 3.0], &[4]);
1324 let indices = Array::from_slice(&[1u32, 3], &[2]);
1325 let out = gather_single(&input, &indices, 0, &[1]).unwrap();
1326 let expected = array!([[1.0f32], [3.0]]);
1327 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1328 }
1329
1330 #[test]
1331 fn test_masked_scatter() {
1332 let input = Array::from_slice(&[1.0f32, 2.0, 3.0, 4.0], &[4]);
1334 let mask = Array::from_slice(&[true, false, true, false], &[4]);
1335 let src = Array::from_slice(&[10.0f32, 20.0], &[2]);
1336 let out = masked_scatter(&input, &mask, &src).unwrap();
1337 let expected = array!([10.0f32, 2.0, 20.0, 4.0]);
1338 assert!(out.all_close(&expected, 1e-5, 1e-5, None).unwrap());
1339 }
1340}