Skip to main content

mlx_rs/ops/indexing/
indexmut_impl.rs

1use std::borrow::Cow;
2
3use smallvec::{smallvec, SmallVec};
4
5use crate::{
6    constants::DEFAULT_STACK_VEC_LEN,
7    error::Result,
8    ops::{
9        broadcast_arrays, broadcast_to,
10        indexing::{count_non_new_axis_operations, expand_ellipsis_operations},
11        reshape,
12    },
13    utils::{resolve_index_signed_unchecked, VectorArray},
14    with_stream, Array, Stream,
15};
16
17use super::{
18    ArrayIndex, ArrayIndexOp, Guarded, IndexUpdateError, RangeIndex, TryIndexMutOp, UpdateMode,
19};
20
21fn non_null_i32_ptr(values: &[i32]) -> *const i32 {
22    static DUMMY: i32 = 0;
23    values.first().map_or(&DUMMY, |value| value) as *const i32
24}
25
26impl Array {
27    pub(crate) fn slice_update_device(
28        &self,
29        update: &Array,
30        starts: &[i32],
31        ends: &[i32],
32        strides: &[i32],
33        mode: UpdateMode,
34        stream: impl AsRef<Stream>,
35    ) -> std::result::Result<Array, IndexUpdateError> {
36        Array::try_from_op(|res| unsafe {
37            let start = non_null_i32_ptr(starts);
38            let stop = non_null_i32_ptr(ends);
39            let strides_ptr = non_null_i32_ptr(strides);
40            match mode {
41                UpdateMode::Replace => mlx_sys::mlx_slice_update(
42                    res,
43                    self.as_ptr(),
44                    update.as_ptr(),
45                    start,
46                    starts.len(),
47                    stop,
48                    ends.len(),
49                    strides_ptr,
50                    strides.len(),
51                    stream.as_ref().as_ptr(),
52                ),
53                UpdateMode::Add => mlx_sys::mlx_slice_update_add(
54                    res,
55                    self.as_ptr(),
56                    update.as_ptr(),
57                    start,
58                    starts.len(),
59                    stop,
60                    ends.len(),
61                    strides_ptr,
62                    strides.len(),
63                    stream.as_ref().as_ptr(),
64                ),
65                UpdateMode::Min => mlx_sys::mlx_slice_update_min(
66                    res,
67                    self.as_ptr(),
68                    update.as_ptr(),
69                    start,
70                    starts.len(),
71                    stop,
72                    ends.len(),
73                    strides_ptr,
74                    strides.len(),
75                    stream.as_ref().as_ptr(),
76                ),
77                UpdateMode::Max => mlx_sys::mlx_slice_update_max(
78                    res,
79                    self.as_ptr(),
80                    update.as_ptr(),
81                    start,
82                    starts.len(),
83                    stop,
84                    ends.len(),
85                    strides_ptr,
86                    strides.len(),
87                    stream.as_ref().as_ptr(),
88                ),
89                UpdateMode::Product => mlx_sys::mlx_slice_update_prod(
90                    res,
91                    self.as_ptr(),
92                    update.as_ptr(),
93                    start,
94                    starts.len(),
95                    stop,
96                    ends.len(),
97                    strides_ptr,
98                    strides.len(),
99                    stream.as_ref().as_ptr(),
100                ),
101            }
102        })
103        .map_err(IndexUpdateError::from)
104    }
105}
106
107// See `updateSlice` in the swift binding or `mlx_slice_update` in the python binding
108fn update_slice(
109    src: &Array,
110    operations: &[ArrayIndexOp],
111    update: &Array,
112    mode: UpdateMode,
113    stream: impl AsRef<Stream>,
114) -> std::result::Result<Option<Array>, IndexUpdateError> {
115    let ndim = src.ndim();
116    if ndim == 0 && count_non_new_axis_operations(operations) > 0 {
117        return Ok(None);
118    }
119
120    // Remove leading singletons dimensions from the update
121    let mut update = remove_leading_singleton_dimensions(update, &stream)?;
122
123    // Build slice update params
124    let mut starts: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = smallvec![0; ndim];
125    let mut ends: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = SmallVec::from_slice(src.shape());
126    let mut strides: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = smallvec![1; ndim];
127
128    // If it's just a simple slice, just do a slice update and return
129    if operations.len() == 1 {
130        if let ArrayIndexOp::Slice(range_index) = &operations[0] {
131            let size = src.dim(0);
132            starts[0] = range_index.start(size);
133            ends[0] = range_index.end(size);
134            strides[0] = range_index.stride();
135
136            return Ok(Some(src.slice_update_device(
137                &update, &starts, &ends, &strides, mode, &stream,
138            )?));
139        }
140    }
141
142    // Can't route to slice update if any arrays are present
143    if operations.iter().any(|op| op.is_array()) {
144        return Ok(None);
145    }
146
147    // Expand ellipses into a series of ':' (range full) slices
148    let operations = expand_ellipsis_operations(ndim, operations);
149
150    // If no non-None indices return the broadcasted update
151    let non_new_axis_operation_count = count_non_new_axis_operations(&operations);
152    if non_new_axis_operation_count == 0 {
153        update = Cow::Owned(with_stream(stream.as_ref(), || {
154            broadcast_to(&update, src.shape())
155        })?);
156        return Ok(Some(src.slice_update_device(
157            &update, &starts, &ends, &strides, mode, &stream,
158        )?));
159    }
160
161    // Process entries
162    // let mut update_expand_dims: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = SmallVec::new();
163    let mut update_reshape: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = smallvec![0; ndim];
164    let mut axis = src.ndim() - 1;
165    let mut update_axis = update.ndim() as i32 - 1;
166
167    while axis >= non_new_axis_operation_count {
168        if update_axis >= 0 {
169            update_reshape[axis] = update.dim(update_axis);
170            update_axis -= 1;
171        } else {
172            update_reshape[axis] = 1;
173        }
174        axis -= 1;
175    }
176
177    for item in operations.iter().rev() {
178        use ArrayIndexOp::*;
179
180        match item {
181            TakeIndex { index } => {
182                let size = src.dim(axis as i32);
183                let index = if index.is_negative() {
184                    size + index
185                } else {
186                    *index
187                };
188                // SAFETY: axis is always non-negative
189                starts[axis] = index;
190                ends[axis] = index.saturating_add(1);
191
192                update_reshape[axis] = 1;
193                axis = axis.saturating_sub(1);
194            }
195            Slice(slice) => {
196                let size = src.dim(axis as i32);
197                // SAFETY: axis is always non-negative
198                starts[axis] = slice.start(size);
199                ends[axis] = slice.end(size);
200                strides[axis] = slice.stride();
201
202                if update_axis >= 0 {
203                    update_reshape[axis] = update.dim(update_axis);
204                    update_axis = update_axis.saturating_sub(1);
205                } else {
206                    update_reshape[axis] = 1;
207                }
208                axis = axis.saturating_sub(1);
209            }
210            ExpandDims => {}
211            Ellipsis | TakeArray { indices: _ } | TakeArrayRef { indices: _ } => {
212                panic!("unexpected item in operations")
213            }
214        }
215    }
216
217    if update.shape() != &update_reshape[..] {
218        update = Cow::Owned(with_stream(stream.as_ref(), || {
219            reshape(update, &update_reshape)
220        })?);
221    }
222
223    Ok(Some(src.slice_update_device(
224        &update, &starts, &ends, &strides, mode, &stream,
225    )?))
226}
227
228// See `leadingSingletonDimensionsRemoved` in the swift binding
229fn remove_leading_singleton_dimensions(
230    a: &Array,
231    stream: impl AsRef<Stream>,
232) -> Result<Cow<'_, Array>> {
233    let shape = a.shape();
234    let mut new_shape: Vec<_> = shape.iter().skip_while(|&&dim| dim == 1).cloned().collect();
235    if shape != new_shape {
236        if new_shape.is_empty() {
237            new_shape = vec![1];
238        }
239        Ok(Cow::Owned(with_stream(stream.as_ref(), || {
240            a.reshape(&new_shape)
241        })?))
242    } else {
243        Ok(Cow::Borrowed(a))
244    }
245}
246
247struct ScatterArgs<'a> {
248    indices: SmallVec<[Cow<'a, Array>; DEFAULT_STACK_VEC_LEN]>,
249    update: Array,
250    axes: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]>,
251}
252
253/// See `scatterArguments` in the swift binding
254fn scatter_args<'a>(
255    src: &'a Array,
256    operations: &'a [ArrayIndexOp],
257    update: &Array,
258    stream: impl AsRef<Stream>,
259) -> Result<ScatterArgs<'a>> {
260    use ArrayIndexOp::*;
261
262    if operations.len() == 1 {
263        return match &operations[0] {
264            TakeIndex { index } => scatter_args_index(src, *index, update, stream),
265            TakeArray { indices } => {
266                scatter_args_array(src, Cow::Borrowed(indices), update, stream)
267            }
268            TakeArrayRef { indices } => {
269                scatter_args_array(src, Cow::Borrowed(indices), update, stream)
270            }
271            Slice(range_index) => scatter_args_slice(src, range_index, update, stream),
272            ExpandDims => Ok(ScatterArgs {
273                indices: smallvec![],
274                update: with_stream(stream.as_ref(), || broadcast_to(update, src.shape()))?,
275                axes: smallvec![],
276            }),
277            Ellipsis => panic!("Unable to update array with ellipsis argument"),
278        };
279    }
280
281    scatter_args_nd(src, operations, update, stream)
282}
283
284fn scatter_args_index<'a>(
285    src: &'a Array,
286    index: i32,
287    update: &Array,
288    stream: impl AsRef<Stream>,
289) -> Result<ScatterArgs<'a>> {
290    // mlx_scatter_args_index
291
292    // Remove any leading singleton dimensions from the update
293    // and then broadcast update to shape of src[0, ...]
294    let update = remove_leading_singleton_dimensions(update, &stream)?;
295
296    let mut shape: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = SmallVec::from_slice(src.shape());
297    shape[0] = 1;
298
299    Ok(ScatterArgs {
300        indices: smallvec![Cow::Owned(Array::from_int(resolve_index_signed_unchecked(
301            index,
302            src.dim(0)
303        )))],
304        update: with_stream(stream.as_ref(), || broadcast_to(&update, &shape))?,
305        axes: smallvec![0],
306    })
307}
308
309fn scatter_args_array<'a>(
310    src: &'a Array,
311    a: Cow<'a, Array>,
312    update: &Array,
313    stream: impl AsRef<Stream>,
314) -> Result<ScatterArgs<'a>> {
315    // mlx_scatter_args_array
316
317    // trim leading singleton dimensions
318    let update = remove_leading_singleton_dimensions(update, &stream)?;
319
320    // The update shape must broadcast with indices.shape + [1] + src.shape[1:]
321    let mut update_shape: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = a
322        .shape()
323        .iter()
324        .chain(src.shape().iter().skip(1))
325        .cloned()
326        .collect();
327    let update = with_stream(stream.as_ref(), || broadcast_to(&update, &update_shape))?;
328
329    update_shape.insert(a.ndim(), 1);
330    let update = with_stream(stream.as_ref(), || update.reshape(&update_shape))?;
331
332    Ok(ScatterArgs {
333        indices: smallvec![a],
334        update,
335        axes: smallvec![0],
336    })
337}
338
339fn scatter_args_slice<'a>(
340    src: &'a Array,
341    range_index: &'a RangeIndex,
342    update: &Array,
343    stream: impl AsRef<Stream>,
344) -> Result<ScatterArgs<'a>> {
345    // mlx_scatter_args_slice
346
347    // if none slice is requested braodcast the update to the src size and return it
348    if range_index.is_full() {
349        let update = remove_leading_singleton_dimensions(update, &stream)?;
350
351        return Ok(ScatterArgs {
352            indices: smallvec![],
353            update: with_stream(stream.as_ref(), || broadcast_to(&update, src.shape()))?,
354            axes: smallvec![],
355        });
356    }
357
358    let size = src.dim(0);
359    let start = range_index.start(size);
360    let end = range_index.end(size);
361    let stride = range_index.stride();
362
363    // If simple stride
364    if stride == 1 {
365        let update = remove_leading_singleton_dimensions(update, &stream)?;
366
367        // Broadcast update to slice size
368        let update_broadcast_shape: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = (1..end - start)
369            .chain(src.shape().iter().skip(1).cloned())
370            .collect();
371        let update = with_stream(stream.as_ref(), || {
372            broadcast_to(&update, &update_broadcast_shape)
373        })?;
374
375        let indices = Array::from_slice(&[start], &[1]);
376        Ok(ScatterArgs {
377            indices: smallvec![Cow::Owned(indices)],
378            update,
379            axes: smallvec![0],
380        })
381    } else {
382        // stride != 1, convert the slice to an array
383        let a_vals = strided_range_to_vec(start, end, stride);
384        let a = Array::from_slice(&a_vals, &[a_vals.len() as i32]);
385
386        scatter_args_array(src, Cow::Owned(a), update, stream)
387    }
388}
389
390fn scatter_args_nd<'a>(
391    src: &'a Array,
392    operations: &[ArrayIndexOp],
393    update: &Array,
394    stream: impl AsRef<Stream>,
395) -> Result<ScatterArgs<'a>> {
396    use ArrayIndexOp::*;
397
398    // mlx_scatter_args_nd
399
400    let shape = src.shape();
401
402    let operations = expand_ellipsis_operations(src.ndim(), operations);
403    let update = remove_leading_singleton_dimensions(update, &stream)?;
404
405    // If no non-newAxis indices return the broadcasted update
406    let non_new_axis_operation_count = count_non_new_axis_operations(&operations);
407    if non_new_axis_operation_count == 0 {
408        return Ok(ScatterArgs {
409            indices: smallvec![],
410            update: with_stream(stream.as_ref(), || broadcast_to(&update, shape))?,
411            axes: smallvec![],
412        });
413    }
414
415    // Analyse the types of the indices
416    let mut max_dims = 0;
417    let mut arrays_first = false;
418    let mut count_new_axis: i32 = 0;
419    let mut count_slices: i32 = 0;
420    let mut count_arrays: i32 = 0;
421    let mut count_strided_slices: i32 = 0;
422    let mut count_simple_slices_post: i32 = 0;
423
424    let mut have_array = false;
425    let mut have_non_array = false;
426
427    macro_rules! analyze_indices_take_array {
428        ($indices:ident) => {
429            have_array = true;
430            if have_array && have_non_array {
431                arrays_first = true;
432            }
433            max_dims = $indices.ndim().max(max_dims);
434            count_arrays = count_arrays.saturating_add(1);
435            count_simple_slices_post = 0;
436        };
437    }
438
439    for item in operations.iter() {
440        match item {
441            TakeIndex { index: _ } => {
442                // ignore
443            }
444            Slice(range_index) => {
445                have_non_array = have_array;
446                count_slices = count_slices.saturating_add(1);
447                if range_index.stride() != 1 {
448                    count_strided_slices = count_strided_slices.saturating_add(1);
449                    count_simple_slices_post = 0;
450                } else {
451                    count_simple_slices_post = count_simple_slices_post.saturating_add(1);
452                }
453            }
454            TakeArray { indices } => {
455                analyze_indices_take_array!(indices);
456            }
457            TakeArrayRef { indices } => {
458                analyze_indices_take_array!(indices);
459            }
460            ExpandDims => {
461                have_non_array = true;
462                count_new_axis = count_new_axis.saturating_add(1);
463            }
464            Ellipsis => panic!("Unexpected item ellipsis in scatter_args_nd"),
465        }
466    }
467
468    // We have index dims for the arrays, strided slices (implemented as arrays), none
469    let mut index_dims = (max_dims + count_new_axis as usize + count_slices as usize)
470        .saturating_sub(count_simple_slices_post as usize);
471
472    // If we have simple non-strided slices, we also attach an index for that
473    if index_dims == 0 {
474        index_dims = 1;
475    }
476
477    // Go over each index type and translate to the needed scatter args
478    let mut array_indices: SmallVec<[Array; DEFAULT_STACK_VEC_LEN]> =
479        SmallVec::with_capacity(operations.len());
480    let mut slice_number: i32 = 0;
481    let mut array_number: i32 = 0;
482    let mut axis: i32 = 0;
483
484    // We collect the shapes of the slices and updates during this process
485    let mut update_shape = vec![1; non_new_axis_operation_count];
486    let mut slice_shapes: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = SmallVec::new();
487
488    macro_rules! update_shapes_take_array {
489        ($indices:ident) => {
490            // Place the arrays in the correct dimension
491            let start = if arrays_first {
492                max_dims - $indices.ndim()
493            } else {
494                // SAFETY: slice_number is never decremented and should be non-negative
495                slice_number as usize + max_dims - $indices.ndim()
496            };
497            let mut new_shape = vec![1; index_dims];
498
499            for j in 0..$indices.ndim() {
500                new_shape[start + j] = $indices.dim(j as i32);
501            }
502
503            array_indices.push(with_stream(stream.as_ref(), || {
504                $indices.reshape(&new_shape)
505            })?);
506            array_number = array_number.saturating_add(1);
507
508            if !arrays_first && array_number == count_arrays {
509                slice_number = slice_number.saturating_add_unsigned(max_dims as u32);
510            }
511
512            // Add the shape to the update
513            update_shape[axis as usize] = 1;
514            axis = axis.saturating_add(1);
515        };
516    }
517
518    for item in operations.iter() {
519        match item {
520            TakeIndex { index } => {
521                let resolved_index = resolve_index_signed_unchecked(*index, src.dim(axis));
522                array_indices.push(Array::from_int(resolved_index));
523                // SAFETY: axis is always non-negative
524                update_shape[axis as usize] = 1;
525                axis = axis.saturating_add(1);
526            }
527            Slice(range_index) => {
528                let size = src.dim(axis);
529                let start = range_index.absolute_start(size);
530                let end = range_index.absolute_end(size);
531                let stride = range_index.stride();
532
533                let mut index_shape = vec![1; index_dims];
534
535                // If it's a simple slice, we only need to add the start index
536                if array_number >= count_arrays && count_strided_slices <= 0 && stride == 1 {
537                    let index = with_stream(stream.as_ref(), || {
538                        Array::from_int(start).reshape(&index_shape)
539                    })?;
540                    let slice_shape_entry = end - start;
541                    slice_shapes.push(slice_shape_entry);
542                    array_indices.push(index);
543
544                    // Add the shape to the update
545                    update_shape[axis as usize] = slice_shape_entry;
546                } else {
547                    // Otherwise we expand the slice into indices using arange
548                    let index_vals = strided_range_to_vec(start, end, stride);
549                    let index = Array::from_slice(&index_vals, &[index_vals.len() as i32]);
550                    let location = if arrays_first {
551                        slice_number.saturating_add(max_dims as i32)
552                    } else {
553                        slice_number
554                    };
555                    index_shape[location as usize] = index.size() as i32;
556                    array_indices.push(with_stream(stream.as_ref(), || {
557                        index.reshape(&index_shape)
558                    })?);
559
560                    slice_number = slice_number.saturating_add(1);
561                    count_strided_slices = count_strided_slices.saturating_sub(1);
562
563                    // Add the shape to the update
564                    update_shape[axis as usize] = 1;
565                }
566
567                axis = axis.saturating_add(1);
568            }
569            TakeArray { indices } => {
570                update_shapes_take_array!(indices);
571            }
572            TakeArrayRef { indices } => {
573                update_shapes_take_array!(indices);
574            }
575            ExpandDims => slice_number = slice_number.saturating_add(1),
576            Ellipsis => panic!("Unexpected item ellipsis in scatter_args_nd"),
577        }
578    }
579
580    // Broadcast the update to the indices and slices
581    let array_indices = with_stream(stream.as_ref(), || broadcast_arrays(&array_indices))?;
582    let update_shape_broadcast: Vec<_> = array_indices[0]
583        .shape()
584        .iter()
585        .chain(slice_shapes.iter())
586        .chain(src.shape().iter().skip(non_new_axis_operation_count))
587        .cloned()
588        .collect();
589    let update = with_stream(stream.as_ref(), || {
590        broadcast_to(&update, &update_shape_broadcast)
591    })?;
592
593    // Reshape the update with the size-1 dims for the int and array indices
594    let update_reshape: Vec<_> = array_indices[0]
595        .shape()
596        .iter()
597        .chain(update_shape.iter())
598        .chain(src.shape().iter().skip(non_new_axis_operation_count))
599        .cloned()
600        .collect();
601
602    let update = with_stream(stream.as_ref(), || update.reshape(&update_reshape))?;
603
604    let array_indices_len = array_indices.len();
605
606    let indices = array_indices.into_iter().map(Cow::Owned).collect();
607    Ok(ScatterArgs {
608        indices,
609        update,
610        axes: (0..array_indices_len as i32).collect(),
611    })
612}
613
614fn strided_range_to_vec(start: i32, exclusive_end: i32, stride: i32) -> Vec<i32> {
615    let estimated_capacity = (exclusive_end - start).abs() / stride.abs();
616    let mut vec = Vec::with_capacity(estimated_capacity as usize);
617    let mut current = start;
618
619    if stride.is_negative() {
620        while current > exclusive_end {
621            vec.push(current);
622            current += stride;
623        }
624    } else {
625        while current < exclusive_end {
626            vec.push(current);
627            current += stride;
628        }
629    }
630
631    vec
632}
633
634unsafe fn scatter_device(
635    a: &Array,
636    indices: &[impl AsRef<Array>],
637    updates: &Array,
638    axes: &[i32],
639    mode: UpdateMode,
640    stream: impl AsRef<Stream>,
641) -> std::result::Result<Array, IndexUpdateError> {
642    let indices_vector = VectorArray::try_from_iter(indices.iter())?;
643
644    Array::try_from_op(|res| unsafe {
645        let axes_ptr = non_null_i32_ptr(axes);
646        match mode {
647            UpdateMode::Replace => mlx_sys::mlx_scatter(
648                res,
649                a.as_ptr(),
650                indices_vector.as_ptr(),
651                updates.as_ptr(),
652                axes_ptr,
653                axes.len(),
654                stream.as_ref().as_ptr(),
655            ),
656            UpdateMode::Add => mlx_sys::mlx_scatter_add(
657                res,
658                a.as_ptr(),
659                indices_vector.as_ptr(),
660                updates.as_ptr(),
661                axes_ptr,
662                axes.len(),
663                stream.as_ref().as_ptr(),
664            ),
665            UpdateMode::Min => mlx_sys::mlx_scatter_min(
666                res,
667                a.as_ptr(),
668                indices_vector.as_ptr(),
669                updates.as_ptr(),
670                axes_ptr,
671                axes.len(),
672                stream.as_ref().as_ptr(),
673            ),
674            UpdateMode::Max => mlx_sys::mlx_scatter_max(
675                res,
676                a.as_ptr(),
677                indices_vector.as_ptr(),
678                updates.as_ptr(),
679                axes_ptr,
680                axes.len(),
681                stream.as_ref().as_ptr(),
682            ),
683            UpdateMode::Product => mlx_sys::mlx_scatter_prod(
684                res,
685                a.as_ptr(),
686                indices_vector.as_ptr(),
687                updates.as_ptr(),
688                axes_ptr,
689                axes.len(),
690                stream.as_ref().as_ptr(),
691            ),
692        }
693    })
694    .map_err(IndexUpdateError::from)
695}
696
697fn validate_strides(operations: &[ArrayIndexOp]) -> std::result::Result<(), IndexUpdateError> {
698    let mut axis = 0;
699    for operation in operations {
700        match operation {
701            ArrayIndexOp::Slice(range) => {
702                if range.stride() == 0 {
703                    return Err(IndexUpdateError::ZeroStride { axis });
704                }
705                axis += 1;
706            }
707            ArrayIndexOp::TakeIndex { .. }
708            | ArrayIndexOp::TakeArray { .. }
709            | ArrayIndexOp::TakeArrayRef { .. } => axis += 1,
710            ArrayIndexOp::ExpandDims => {}
711            ArrayIndexOp::Ellipsis => unreachable!("ellipsis is expanded before validation"),
712        }
713    }
714    Ok(())
715}
716
717fn validate_index_structure(
718    ndim: usize,
719    operations: &[ArrayIndexOp],
720) -> std::result::Result<(), IndexUpdateError> {
721    let ellipsis_count = operations
722        .iter()
723        .filter(|operation| matches!(operation, ArrayIndexOp::Ellipsis))
724        .count();
725    if ellipsis_count > 1 {
726        return Err(IndexUpdateError::Exception(
727            crate::error::Exception::custom("multiple ellipses are not supported"),
728        ));
729    }
730
731    let source_axes = operations
732        .iter()
733        .filter(|operation| !matches!(operation, ArrayIndexOp::ExpandDims | ArrayIndexOp::Ellipsis))
734        .count();
735    if source_axes > ndim {
736        return Err(IndexUpdateError::Exception(
737            crate::error::Exception::custom(format!(
738                "too many indices for array with {ndim} dimensions"
739            )),
740        ));
741    }
742
743    Ok(())
744}
745
746pub(super) fn try_index_update_operations(
747    source: &Array,
748    operations: &[ArrayIndexOp],
749    update: &Array,
750    mode: UpdateMode,
751) -> std::result::Result<Array, IndexUpdateError> {
752    let stream = Stream::thread_local_or_default();
753    validate_index_structure(source.ndim(), operations)?;
754    let operations = expand_ellipsis_operations(source.ndim(), operations);
755    validate_strides(&operations)?;
756
757    if let Some(result) = update_slice(source, &operations, update, mode, &stream)? {
758        return Ok(result);
759    }
760
761    let ScatterArgs {
762        indices,
763        update,
764        axes,
765    } = scatter_args(source, &operations, update, &stream)?;
766    if indices.is_empty() {
767        return Ok(update);
768    }
769
770    let result = unsafe { scatter_device(source, &indices, &update, &axes, mode, stream)? };
771    drop(indices);
772    Ok(result)
773}
774
775impl Array {
776    fn try_index_mut_device_inner(
777        &mut self,
778        operations: &[ArrayIndexOp],
779        update: &Array,
780        stream: impl AsRef<Stream>,
781    ) -> Result<()> {
782        let result = with_stream(stream.as_ref(), || {
783            try_index_update_operations(self, operations, update, UpdateMode::Replace)
784        })
785        .map_err(|error| match error {
786            IndexUpdateError::ZeroStride { .. } => {
787                crate::error::Exception::custom(error.to_string())
788            }
789            IndexUpdateError::Exception(error) => error,
790        })?;
791        *self = result;
792        Ok(())
793    }
794}
795
796impl<'a, Val> TryIndexMutOp<&'a [ArrayIndexOp<'a>], Val> for Array
797where
798    Val: AsRef<Array>,
799{
800    fn try_index_mut_device(
801        &mut self,
802        i: &'a [ArrayIndexOp<'a>],
803        val: Val,
804        stream: impl AsRef<Stream>,
805    ) -> Result<()> {
806        let update = val.as_ref();
807        self.try_index_mut_device_inner(i, update, stream)
808    }
809}
810
811impl<A, Val> TryIndexMutOp<A, Val> for Array
812where
813    for<'a> A: ArrayIndex<'a>,
814    Val: AsRef<Array>,
815{
816    fn try_index_mut_device(&mut self, i: A, val: Val, stream: impl AsRef<Stream>) -> Result<()> {
817        let operations = [i.index_op()];
818        let update = val.as_ref();
819        self.try_index_mut_device_inner(&operations, update, stream)
820    }
821}
822
823impl<'a, A, Val> TryIndexMutOp<(A,), Val> for Array
824where
825    A: ArrayIndex<'a>,
826    Val: AsRef<Array>,
827{
828    fn try_index_mut_device(
829        &mut self,
830        (i,): (A,),
831        val: Val,
832        stream: impl AsRef<Stream>,
833    ) -> Result<()> {
834        let operations = [i.index_op()];
835        let update = val.as_ref();
836        self.try_index_mut_device_inner(&operations, update, stream)
837    }
838}
839
840impl<'a, 'b, A, B, Val> TryIndexMutOp<(A, B), Val> for Array
841where
842    A: ArrayIndex<'a>,
843    B: ArrayIndex<'b>,
844    Val: AsRef<Array>,
845{
846    fn try_index_mut_device(
847        &mut self,
848        i: (A, B),
849        val: Val,
850        stream: impl AsRef<Stream>,
851    ) -> Result<()> {
852        let operations = [i.0.index_op(), i.1.index_op()];
853        let update = val.as_ref();
854        self.try_index_mut_device_inner(&operations, update, stream)
855    }
856}
857
858impl<'a, 'b, 'c, A, B, C, Val> TryIndexMutOp<(A, B, C), Val> for Array
859where
860    A: ArrayIndex<'a>,
861    B: ArrayIndex<'b>,
862    C: ArrayIndex<'c>,
863    Val: AsRef<Array>,
864{
865    fn try_index_mut_device(
866        &mut self,
867        i: (A, B, C),
868        val: Val,
869        stream: impl AsRef<Stream>,
870    ) -> Result<()> {
871        let operations = [i.0.index_op(), i.1.index_op(), i.2.index_op()];
872        let update = val.as_ref();
873        self.try_index_mut_device_inner(&operations, update, stream)
874    }
875}
876
877impl<'a, 'b, 'c, 'd, A, B, C, D, Val> TryIndexMutOp<(A, B, C, D), Val> for Array
878where
879    A: ArrayIndex<'a>,
880    B: ArrayIndex<'b>,
881    C: ArrayIndex<'c>,
882    D: ArrayIndex<'d>,
883    Val: AsRef<Array>,
884{
885    fn try_index_mut_device(
886        &mut self,
887        i: (A, B, C, D),
888        val: Val,
889        stream: impl AsRef<Stream>,
890    ) -> Result<()> {
891        let operations = [
892            i.0.index_op(),
893            i.1.index_op(),
894            i.2.index_op(),
895            i.3.index_op(),
896        ];
897        let update = val.as_ref();
898        self.try_index_mut_device_inner(&operations, update, stream)
899    }
900}
901
902impl<'a, 'b, 'c, 'd, 'e, A, B, C, D, E, Val> TryIndexMutOp<(A, B, C, D, E), Val> for Array
903where
904    A: ArrayIndex<'a>,
905    B: ArrayIndex<'b>,
906    C: ArrayIndex<'c>,
907    D: ArrayIndex<'d>,
908    E: ArrayIndex<'e>,
909    Val: AsRef<Array>,
910{
911    fn try_index_mut_device(
912        &mut self,
913        i: (A, B, C, D, E),
914        val: Val,
915        stream: impl AsRef<Stream>,
916    ) -> Result<()> {
917        let operations = [
918            i.0.index_op(),
919            i.1.index_op(),
920            i.2.index_op(),
921            i.3.index_op(),
922            i.4.index_op(),
923        ];
924        let update = val.as_ref();
925        self.try_index_mut_device_inner(&operations, update, stream)
926    }
927}
928
929impl<'a, 'b, 'c, 'd, 'e, 'f, A, B, C, D, E, F, Val> TryIndexMutOp<(A, B, C, D, E, F), Val> for Array
930where
931    A: ArrayIndex<'a>,
932    B: ArrayIndex<'b>,
933    C: ArrayIndex<'c>,
934    D: ArrayIndex<'d>,
935    E: ArrayIndex<'e>,
936    F: ArrayIndex<'f>,
937    Val: AsRef<Array>,
938{
939    fn try_index_mut_device(
940        &mut self,
941        i: (A, B, C, D, E, F),
942        val: Val,
943        stream: impl AsRef<Stream>,
944    ) -> Result<()> {
945        let operations = [
946            i.0.index_op(),
947            i.1.index_op(),
948            i.2.index_op(),
949            i.3.index_op(),
950            i.4.index_op(),
951            i.5.index_op(),
952        ];
953        let update = val.as_ref();
954        self.try_index_mut_device_inner(&operations, update, stream)
955    }
956}
957
958impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, A, B, C, D, E, F, G, Val> TryIndexMutOp<(A, B, C, D, E, F, G), Val>
959    for Array
960where
961    A: ArrayIndex<'a>,
962    B: ArrayIndex<'b>,
963    C: ArrayIndex<'c>,
964    D: ArrayIndex<'d>,
965    E: ArrayIndex<'e>,
966    F: ArrayIndex<'f>,
967    G: ArrayIndex<'g>,
968    Val: AsRef<Array>,
969{
970    fn try_index_mut_device(
971        &mut self,
972        i: (A, B, C, D, E, F, G),
973        val: Val,
974        stream: impl AsRef<Stream>,
975    ) -> Result<()> {
976        let operations = [
977            i.0.index_op(),
978            i.1.index_op(),
979            i.2.index_op(),
980            i.3.index_op(),
981            i.4.index_op(),
982            i.5.index_op(),
983            i.6.index_op(),
984        ];
985        let update = val.as_ref();
986        self.try_index_mut_device_inner(&operations, update, stream)
987    }
988}
989
990impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, A, B, C, D, E, F, G, H, Val>
991    TryIndexMutOp<(A, B, C, D, E, F, G, H), Val> for Array
992where
993    A: ArrayIndex<'a>,
994    B: ArrayIndex<'b>,
995    C: ArrayIndex<'c>,
996    D: ArrayIndex<'d>,
997    E: ArrayIndex<'e>,
998    F: ArrayIndex<'f>,
999    G: ArrayIndex<'g>,
1000    H: ArrayIndex<'h>,
1001    Val: AsRef<Array>,
1002{
1003    fn try_index_mut_device(
1004        &mut self,
1005        i: (A, B, C, D, E, F, G, H),
1006        val: Val,
1007        stream: impl AsRef<Stream>,
1008    ) -> Result<()> {
1009        let operations = [
1010            i.0.index_op(),
1011            i.1.index_op(),
1012            i.2.index_op(),
1013            i.3.index_op(),
1014            i.4.index_op(),
1015            i.5.index_op(),
1016            i.6.index_op(),
1017            i.7.index_op(),
1018        ];
1019        let update = val.as_ref();
1020        self.try_index_mut_device_inner(&operations, update, stream)
1021    }
1022}
1023
1024impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, A, B, C, D, E, F, G, H, I, Val>
1025    TryIndexMutOp<(A, B, C, D, E, F, G, H, I), Val> for Array
1026where
1027    A: ArrayIndex<'a>,
1028    B: ArrayIndex<'b>,
1029    C: ArrayIndex<'c>,
1030    D: ArrayIndex<'d>,
1031    E: ArrayIndex<'e>,
1032    F: ArrayIndex<'f>,
1033    G: ArrayIndex<'g>,
1034    H: ArrayIndex<'h>,
1035    I: ArrayIndex<'i>,
1036    Val: AsRef<Array>,
1037{
1038    fn try_index_mut_device(
1039        &mut self,
1040        i: (A, B, C, D, E, F, G, H, I),
1041        val: Val,
1042        stream: impl AsRef<Stream>,
1043    ) -> Result<()> {
1044        let operations = [
1045            i.0.index_op(),
1046            i.1.index_op(),
1047            i.2.index_op(),
1048            i.3.index_op(),
1049            i.4.index_op(),
1050            i.5.index_op(),
1051            i.6.index_op(),
1052            i.7.index_op(),
1053            i.8.index_op(),
1054        ];
1055        let update = val.as_ref();
1056        self.try_index_mut_device_inner(&operations, update, stream)
1057    }
1058}
1059
1060impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, A, B, C, D, E, F, G, H, I, J, Val>
1061    TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J), Val> for Array
1062where
1063    A: ArrayIndex<'a>,
1064    B: ArrayIndex<'b>,
1065    C: ArrayIndex<'c>,
1066    D: ArrayIndex<'d>,
1067    E: ArrayIndex<'e>,
1068    F: ArrayIndex<'f>,
1069    G: ArrayIndex<'g>,
1070    H: ArrayIndex<'h>,
1071    I: ArrayIndex<'i>,
1072    J: ArrayIndex<'j>,
1073    Val: AsRef<Array>,
1074{
1075    fn try_index_mut_device(
1076        &mut self,
1077        i: (A, B, C, D, E, F, G, H, I, J),
1078        val: Val,
1079        stream: impl AsRef<Stream>,
1080    ) -> Result<()> {
1081        let operations = [
1082            i.0.index_op(),
1083            i.1.index_op(),
1084            i.2.index_op(),
1085            i.3.index_op(),
1086            i.4.index_op(),
1087            i.5.index_op(),
1088            i.6.index_op(),
1089            i.7.index_op(),
1090            i.8.index_op(),
1091            i.9.index_op(),
1092        ];
1093        let update = val.as_ref();
1094        self.try_index_mut_device_inner(&operations, update, stream)
1095    }
1096}
1097
1098impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, A, B, C, D, E, F, G, H, I, J, K, Val>
1099    TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J, K), Val> for Array
1100where
1101    A: ArrayIndex<'a>,
1102    B: ArrayIndex<'b>,
1103    C: ArrayIndex<'c>,
1104    D: ArrayIndex<'d>,
1105    E: ArrayIndex<'e>,
1106    F: ArrayIndex<'f>,
1107    G: ArrayIndex<'g>,
1108    H: ArrayIndex<'h>,
1109    I: ArrayIndex<'i>,
1110    J: ArrayIndex<'j>,
1111    K: ArrayIndex<'k>,
1112    Val: AsRef<Array>,
1113{
1114    fn try_index_mut_device(
1115        &mut self,
1116        i: (A, B, C, D, E, F, G, H, I, J, K),
1117        val: Val,
1118        stream: impl AsRef<Stream>,
1119    ) -> Result<()> {
1120        let operations = [
1121            i.0.index_op(),
1122            i.1.index_op(),
1123            i.2.index_op(),
1124            i.3.index_op(),
1125            i.4.index_op(),
1126            i.5.index_op(),
1127            i.6.index_op(),
1128            i.7.index_op(),
1129            i.8.index_op(),
1130            i.9.index_op(),
1131            i.10.index_op(),
1132        ];
1133        let update = val.as_ref();
1134        self.try_index_mut_device_inner(&operations, update, stream)
1135    }
1136}
1137
1138impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, A, B, C, D, E, F, G, H, I, J, K, L, Val>
1139    TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J, K, L), Val> for Array
1140where
1141    A: ArrayIndex<'a>,
1142    B: ArrayIndex<'b>,
1143    C: ArrayIndex<'c>,
1144    D: ArrayIndex<'d>,
1145    E: ArrayIndex<'e>,
1146    F: ArrayIndex<'f>,
1147    G: ArrayIndex<'g>,
1148    H: ArrayIndex<'h>,
1149    I: ArrayIndex<'i>,
1150    J: ArrayIndex<'j>,
1151    K: ArrayIndex<'k>,
1152    L: ArrayIndex<'l>,
1153    Val: AsRef<Array>,
1154{
1155    fn try_index_mut_device(
1156        &mut self,
1157        i: (A, B, C, D, E, F, G, H, I, J, K, L),
1158        val: Val,
1159        stream: impl AsRef<Stream>,
1160    ) -> Result<()> {
1161        let operations = [
1162            i.0.index_op(),
1163            i.1.index_op(),
1164            i.2.index_op(),
1165            i.3.index_op(),
1166            i.4.index_op(),
1167            i.5.index_op(),
1168            i.6.index_op(),
1169            i.7.index_op(),
1170            i.8.index_op(),
1171            i.9.index_op(),
1172            i.10.index_op(),
1173            i.11.index_op(),
1174        ];
1175        let update = val.as_ref();
1176        self.try_index_mut_device_inner(&operations, update, stream)
1177    }
1178}
1179
1180impl<
1181        'a,
1182        'b,
1183        'c,
1184        'd,
1185        'e,
1186        'f,
1187        'g,
1188        'h,
1189        'i,
1190        'j,
1191        'k,
1192        'l,
1193        'm,
1194        A,
1195        B,
1196        C,
1197        D,
1198        E,
1199        F,
1200        G,
1201        H,
1202        I,
1203        J,
1204        K,
1205        L,
1206        M,
1207        Val,
1208    > TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J, K, L, M), Val> for Array
1209where
1210    A: ArrayIndex<'a>,
1211    B: ArrayIndex<'b>,
1212    C: ArrayIndex<'c>,
1213    D: ArrayIndex<'d>,
1214    E: ArrayIndex<'e>,
1215    F: ArrayIndex<'f>,
1216    G: ArrayIndex<'g>,
1217    H: ArrayIndex<'h>,
1218    I: ArrayIndex<'i>,
1219    J: ArrayIndex<'j>,
1220    K: ArrayIndex<'k>,
1221    L: ArrayIndex<'l>,
1222    M: ArrayIndex<'m>,
1223    Val: AsRef<Array>,
1224{
1225    fn try_index_mut_device(
1226        &mut self,
1227        i: (A, B, C, D, E, F, G, H, I, J, K, L, M),
1228        val: Val,
1229        stream: impl AsRef<Stream>,
1230    ) -> Result<()> {
1231        let operations = [
1232            i.0.index_op(),
1233            i.1.index_op(),
1234            i.2.index_op(),
1235            i.3.index_op(),
1236            i.4.index_op(),
1237            i.5.index_op(),
1238            i.6.index_op(),
1239            i.7.index_op(),
1240            i.8.index_op(),
1241            i.9.index_op(),
1242            i.10.index_op(),
1243            i.11.index_op(),
1244            i.12.index_op(),
1245        ];
1246        let update = val.as_ref();
1247        self.try_index_mut_device_inner(&operations, update, stream)
1248    }
1249}
1250
1251impl<
1252        'a,
1253        'b,
1254        'c,
1255        'd,
1256        'e,
1257        'f,
1258        'g,
1259        'h,
1260        'i,
1261        'j,
1262        'k,
1263        'l,
1264        'm,
1265        'n,
1266        A,
1267        B,
1268        C,
1269        D,
1270        E,
1271        F,
1272        G,
1273        H,
1274        I,
1275        J,
1276        K,
1277        L,
1278        M,
1279        N,
1280        Val,
1281    > TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J, K, L, M, N), Val> for Array
1282where
1283    A: ArrayIndex<'a>,
1284    B: ArrayIndex<'b>,
1285    C: ArrayIndex<'c>,
1286    D: ArrayIndex<'d>,
1287    E: ArrayIndex<'e>,
1288    F: ArrayIndex<'f>,
1289    G: ArrayIndex<'g>,
1290    H: ArrayIndex<'h>,
1291    I: ArrayIndex<'i>,
1292    J: ArrayIndex<'j>,
1293    K: ArrayIndex<'k>,
1294    L: ArrayIndex<'l>,
1295    M: ArrayIndex<'m>,
1296    N: ArrayIndex<'n>,
1297    Val: AsRef<Array>,
1298{
1299    fn try_index_mut_device(
1300        &mut self,
1301        i: (A, B, C, D, E, F, G, H, I, J, K, L, M, N),
1302        val: Val,
1303        stream: impl AsRef<Stream>,
1304    ) -> Result<()> {
1305        let operations = [
1306            i.0.index_op(),
1307            i.1.index_op(),
1308            i.2.index_op(),
1309            i.3.index_op(),
1310            i.4.index_op(),
1311            i.5.index_op(),
1312            i.6.index_op(),
1313            i.7.index_op(),
1314            i.8.index_op(),
1315            i.9.index_op(),
1316            i.10.index_op(),
1317            i.11.index_op(),
1318            i.12.index_op(),
1319            i.13.index_op(),
1320        ];
1321        let update = val.as_ref();
1322        self.try_index_mut_device_inner(&operations, update, stream)
1323    }
1324}
1325
1326impl<
1327        'a,
1328        'b,
1329        'c,
1330        'd,
1331        'e,
1332        'f,
1333        'g,
1334        'h,
1335        'i,
1336        'j,
1337        'k,
1338        'l,
1339        'm,
1340        'n,
1341        'o,
1342        A,
1343        B,
1344        C,
1345        D,
1346        E,
1347        F,
1348        G,
1349        H,
1350        I,
1351        J,
1352        K,
1353        L,
1354        M,
1355        N,
1356        O,
1357        Val,
1358    > TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O), Val> for Array
1359where
1360    A: ArrayIndex<'a>,
1361    B: ArrayIndex<'b>,
1362    C: ArrayIndex<'c>,
1363    D: ArrayIndex<'d>,
1364    E: ArrayIndex<'e>,
1365    F: ArrayIndex<'f>,
1366    G: ArrayIndex<'g>,
1367    H: ArrayIndex<'h>,
1368    I: ArrayIndex<'i>,
1369    J: ArrayIndex<'j>,
1370    K: ArrayIndex<'k>,
1371    L: ArrayIndex<'l>,
1372    M: ArrayIndex<'m>,
1373    N: ArrayIndex<'n>,
1374    O: ArrayIndex<'o>,
1375    Val: AsRef<Array>,
1376{
1377    fn try_index_mut_device(
1378        &mut self,
1379        i: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O),
1380        val: Val,
1381        stream: impl AsRef<Stream>,
1382    ) -> Result<()> {
1383        let operations = [
1384            i.0.index_op(),
1385            i.1.index_op(),
1386            i.2.index_op(),
1387            i.3.index_op(),
1388            i.4.index_op(),
1389            i.5.index_op(),
1390            i.6.index_op(),
1391            i.7.index_op(),
1392            i.8.index_op(),
1393            i.9.index_op(),
1394            i.10.index_op(),
1395            i.11.index_op(),
1396            i.12.index_op(),
1397            i.13.index_op(),
1398            i.14.index_op(),
1399        ];
1400        let update = val.as_ref();
1401        self.try_index_mut_device_inner(&operations, update, stream)
1402    }
1403}
1404
1405impl<
1406        'a,
1407        'b,
1408        'c,
1409        'd,
1410        'e,
1411        'f,
1412        'g,
1413        'h,
1414        'i,
1415        'j,
1416        'k,
1417        'l,
1418        'm,
1419        'n,
1420        'o,
1421        'p,
1422        A,
1423        B,
1424        C,
1425        D,
1426        E,
1427        F,
1428        G,
1429        H,
1430        I,
1431        J,
1432        K,
1433        L,
1434        M,
1435        N,
1436        O,
1437        P,
1438        Val,
1439    > TryIndexMutOp<(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P), Val> for Array
1440where
1441    A: ArrayIndex<'a>,
1442    B: ArrayIndex<'b>,
1443    C: ArrayIndex<'c>,
1444    D: ArrayIndex<'d>,
1445    E: ArrayIndex<'e>,
1446    F: ArrayIndex<'f>,
1447    G: ArrayIndex<'g>,
1448    H: ArrayIndex<'h>,
1449    I: ArrayIndex<'i>,
1450    J: ArrayIndex<'j>,
1451    K: ArrayIndex<'k>,
1452    L: ArrayIndex<'l>,
1453    M: ArrayIndex<'m>,
1454    N: ArrayIndex<'n>,
1455    O: ArrayIndex<'o>,
1456    P: ArrayIndex<'p>,
1457    Val: AsRef<Array>,
1458{
1459    fn try_index_mut_device(
1460        &mut self,
1461        i: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P),
1462        val: Val,
1463        stream: impl AsRef<Stream>,
1464    ) -> Result<()> {
1465        let operations = [
1466            i.0.index_op(),
1467            i.1.index_op(),
1468            i.2.index_op(),
1469            i.3.index_op(),
1470            i.4.index_op(),
1471            i.5.index_op(),
1472            i.6.index_op(),
1473            i.7.index_op(),
1474            i.8.index_op(),
1475            i.9.index_op(),
1476            i.10.index_op(),
1477            i.11.index_op(),
1478            i.12.index_op(),
1479            i.13.index_op(),
1480            i.14.index_op(),
1481            i.15.index_op(),
1482        ];
1483        let update = val.as_ref();
1484        self.try_index_mut_device_inner(&operations, update, stream)
1485    }
1486}
1487
1488/// The unit tests below are adapted from the Swift binding tests
1489#[cfg(test)]
1490mod tests {
1491    use crate::{
1492        ops::{indexing::*, ones, zeros},
1493        Array,
1494    };
1495
1496    #[test]
1497    fn test_array_mutate_single_index() {
1498        let mut a = Array::from_iter(0i32..12, &[3, 4]);
1499        let new_value = Array::from_int(77);
1500        a.index_mut(1, new_value);
1501
1502        let expected = Array::from_slice(&[0, 1, 2, 3, 77, 77, 77, 77, 8, 9, 10, 11], &[3, 4]);
1503        assert_array_all_close!(a, expected);
1504    }
1505
1506    #[test]
1507    fn test_array_mutate_broadcast_multi_index() {
1508        let mut a = Array::from_iter(0i32..20, &[2, 2, 5]);
1509
1510        // broadcast to a row
1511        a.index_mut((1, 0), Array::from_int(77));
1512
1513        // assign to a row
1514        a.index_mut((0, 0), Array::from_slice(&[55i32, 66, 77, 88, 99], &[5]));
1515
1516        // single element
1517        a.index_mut((0, 1, 3), Array::from_int(123));
1518
1519        let expected = Array::from_slice(
1520            &[
1521                55, 66, 77, 88, 99, 5, 6, 7, 123, 9, 77, 77, 77, 77, 77, 15, 16, 17, 18, 19,
1522            ],
1523            &[2, 2, 5],
1524        );
1525        assert_array_all_close!(a, expected);
1526    }
1527
1528    #[test]
1529    fn test_array_mutate_broadcast_slice() {
1530        let mut a = Array::from_iter(0i32..20, &[2, 2, 5]);
1531
1532        // writing using slices -- this ends up covering two elements
1533        a.index_mut((0..1, 1..2, 2..4), Array::from_int(88));
1534
1535        let expected = Array::from_slice(
1536            &[
1537                0, 1, 2, 3, 4, 5, 6, 88, 88, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
1538            ],
1539            &[2, 2, 5],
1540        );
1541        assert_array_all_close!(a, expected);
1542    }
1543
1544    #[test]
1545    fn test_array_mutate_advanced() {
1546        let mut a = Array::from_iter(0i32..35, &[5, 7]);
1547
1548        let i1 = Array::from_slice(&[0, 2, 4], &[3]);
1549        let i2 = Array::from_slice(&[0, 1, 2], &[3]);
1550
1551        a.index_mut((i1, i2), Array::from_slice(&[100, 200, 300], &[3]));
1552
1553        assert_eq!(a.index((0, 0)).item_exact::<i32>(), 100i32);
1554        assert_eq!(a.index((2, 1)).item_exact::<i32>(), 200i32);
1555        assert_eq!(a.index((4, 2)).item_exact::<i32>(), 300i32);
1556    }
1557
1558    #[test]
1559    fn test_full_index_write_single() {
1560        fn check<I>(index: I, expected_sum: i32)
1561        where
1562            for<'a> I: ArrayIndex<'a>,
1563        {
1564            let mut a = Array::from_iter(0..60, &[3, 4, 5]);
1565
1566            a.index_mut(index, Array::from_int(1));
1567            let sum = a.sum(None).unwrap().item_exact::<i32>();
1568            assert_eq!(sum, expected_sum);
1569        }
1570
1571        // a[...]
1572        // not valid
1573
1574        // a[None]
1575        check(NewAxis, 60);
1576
1577        // a[0]
1578        check(0, 1600);
1579
1580        // a[1:3]
1581        check(1..3, 230);
1582
1583        // i = mx.array([2, 1])
1584        let i = Array::from_slice(&[2, 1], &[2]);
1585
1586        // a[i]
1587        check(i, 230);
1588    }
1589
1590    #[test]
1591    fn test_full_index_write_no_array() {
1592        macro_rules! check {
1593            (($( $i:expr ),*), $sum:expr ) => {
1594                {
1595                    let mut a = Array::from_iter(0..360, &[2, 3, 4, 5, 3]);
1596
1597                    a.index_mut(($($i),*), Array::from_int(1));
1598                    let sum = a.sum(None).unwrap().item_exact::<i32>();
1599                    assert_eq!(sum, $sum);
1600                }
1601            };
1602        }
1603
1604        // a[..., 0] = 1
1605        check!((Ellipsis, 0), 43320);
1606
1607        // a[0, ...] = 1
1608        check!((0, Ellipsis), 48690);
1609
1610        // a[0, ..., 0] = 1
1611        check!((0, Ellipsis, 0), 59370);
1612
1613        // a[..., ::2, :] = 1
1614        check!((Ellipsis, (..).stride_by(2), ..), 26064);
1615
1616        // a[..., None, ::2, -1]
1617        check!((Ellipsis, NewAxis, (..).stride_by(2), -1), 51696);
1618
1619        // a[:, 2:, 0] = 1
1620        check!((.., 2.., 0), 58140);
1621
1622        // a[::-1, :2, 2:, ..., None, ::2] = 1
1623        check!(
1624            (
1625                (..).stride_by(-1),
1626                ..2,
1627                2..,
1628                Ellipsis,
1629                NewAxis,
1630                (..).stride_by(2)
1631            ),
1632            51540
1633        );
1634    }
1635
1636    #[test]
1637    fn test_full_index_write_array() {
1638        // these have an Array as a source of indices and go through the gather path
1639
1640        macro_rules! check {
1641            (($( $i:expr ),*), $sum:expr ) => {
1642                {
1643                    let mut a = Array::from_iter(0..540, &[3, 3, 4, 5, 3]);
1644
1645                    a.index_mut(($($i),*), Array::from_int(1));
1646                    let sum = a.sum(None).unwrap().item_exact::<i32>();
1647                    assert_eq!(sum, $sum);
1648                }
1649            };
1650        }
1651
1652        // i = mx.array([2, 1])
1653        let i = Array::from_slice(&[2, 1], &[2]);
1654
1655        // a[0, i] = 1
1656        check!((0, &i), 131310);
1657
1658        // a[..., i, 0] = 1
1659        check!((Ellipsis, &i, 0), 126378);
1660
1661        // a[i, 0, ...] = 1
1662        check!((&i, 0, Ellipsis), 109710);
1663
1664        // a[i, ..., i] = 1
1665        check!((&i, Ellipsis, &i), 102450);
1666
1667        // a[i, ..., ::2, :] = 1
1668        check!((&i, Ellipsis, (..).stride_by(2), ..), 68094);
1669
1670        // a[..., i, None, ::2, -1] = 1
1671        check!((Ellipsis, &i, NewAxis, (..).stride_by(2), -1), 130977);
1672
1673        // a[:, 2:, i] = 1
1674        check!((.., 2.., &i), 115965);
1675
1676        // a[::-1, :2, i, 2:, ..., None, ::2] = 1
1677        check!(
1678            (
1679                (..).stride_by(-1),
1680                ..2,
1681                i,
1682                2..,
1683                Ellipsis,
1684                NewAxis,
1685                (..).stride_by(2)
1686            ),
1687            128142
1688        );
1689    }
1690
1691    #[test]
1692    fn test_slice_update_with_broadcast() {
1693        let mut xs = zeros::<f32>(&[4, 3, 2]).unwrap();
1694        let x = ones::<f32>(&[4, 2]).unwrap();
1695
1696        let result = xs.try_index_mut((.., 0, ..), x);
1697        assert!(
1698            result.is_ok(),
1699            "Failed to update slice with broadcast: {result:?}"
1700        );
1701    }
1702}