Skip to main content

mlx_rs/ops/
shapes.rs

1use mlx_internal_macros::generate_macro;
2use smallvec::SmallVec;
3
4use crate::{
5    constants::DEFAULT_STACK_VEC_LEN,
6    error::Result,
7    utils::{guard::Guarded, IntoOption, VectorArray},
8    Array, Axes, Stream,
9};
10
11static EMPTY_AXES_DUMMY: [i32; 1] = [0];
12
13/// Options controlling contiguous layout materialization.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
15pub struct ContiguousOptions {
16    /// Permit MLX to retain a column-major layout when it is already suitable.
17    pub allow_col_major: bool,
18}
19
20impl Array {
21    /// Return an array that is row-major contiguous after evaluation.
22    ///
23    /// MLX may share the input buffer when it is already suitable.
24    pub fn contiguous(&self) -> Result<Array> {
25        self.contiguous_with_options(ContiguousOptions::default())
26    }
27
28    /// Materialize a contiguous array according to `options`.
29    ///
30    /// Allowing column-major layout does not force it and does not guarantee that the result can
31    /// be borrowed with [`Array::try_as_slice`].
32    pub fn contiguous_with_options(&self, options: ContiguousOptions) -> Result<Array> {
33        let stream = Stream::thread_local_or_default();
34        Array::try_from_op(|res| unsafe {
35            mlx_sys::mlx_contiguous(
36                res,
37                self.as_ptr(),
38                options.allow_col_major,
39                stream.as_ref().as_ptr(),
40            )
41        })
42    }
43
44    /// Reverse elements along selected axes.
45    ///
46    /// `Axes::All` reverses every axis, while an explicitly empty axis list is an identity.
47    ///
48    /// ```rust
49    /// use mlx_rs::{array, Axes};
50    ///
51    /// let output = array!([[1, 2], [3, 4]]).flip(Axes::Axis(-1)).unwrap();
52    /// assert_eq!(output.shape(), &[2, 2]);
53    /// ```
54    pub fn flip(&self, axes: Axes) -> Result<Array> {
55        let stream = Stream::thread_local_or_default();
56        match axes {
57            Axes::All => Array::try_from_op(|res| unsafe {
58                mlx_sys::mlx_flip(res, self.as_ptr(), stream.as_ref().as_ptr())
59            }),
60            Axes::Axis(axis) => Array::try_from_op(|res| unsafe {
61                mlx_sys::mlx_flip_axis(res, self.as_ptr(), axis, stream.as_ref().as_ptr())
62            }),
63            Axes::Axes(axes) => {
64                let axes_ptr = if axes.is_empty() {
65                    EMPTY_AXES_DUMMY.as_ptr()
66                } else {
67                    axes.as_ptr()
68                };
69                Array::try_from_op(|res| unsafe {
70                    mlx_sys::mlx_flip_axes(
71                        res,
72                        self.as_ptr(),
73                        axes_ptr,
74                        axes.len(),
75                        stream.as_ref().as_ptr(),
76                    )
77                })
78            }
79        }
80    }
81
82    /// Split an array along `axis` and squeeze that axis from every output.
83    ///
84    /// A zero-length selected axis returns an empty vector.
85    ///
86    /// ```rust
87    /// use mlx_rs::array;
88    ///
89    /// let parts = array!([[1, 2], [3, 4]]).unstack(0).unwrap();
90    /// assert_eq!(parts.len(), 2);
91    /// assert_eq!(parts[0].shape(), &[2]);
92    /// ```
93    pub fn unstack(&self, axis: i32) -> Result<Vec<Array>> {
94        let stream = Stream::thread_local_or_default();
95        Vec::<Array>::try_from_op(|res| unsafe {
96            if axis == 0 {
97                mlx_sys::mlx_unstack(res, self.as_ptr(), stream.as_ref().as_ptr())
98            } else {
99                mlx_sys::mlx_unstack_axis(res, self.as_ptr(), axis, stream.as_ref().as_ptr())
100            }
101        })
102    }
103
104    /// See [`expand_dims()`].
105    pub fn expand_dims(&self, axis: i32) -> Result<Array> {
106        expand_dims(self, axis)
107    }
108
109    /// Compatibility shim for [`expand_dims`].
110    #[deprecated(
111        since = "0.26.0",
112        note = "use `with_stream` or `with_device` around `expand_dims`"
113    )]
114    pub fn expand_dims_device(&self, axis: i32, stream: impl AsRef<Stream>) -> Result<Array> {
115        crate::with_stream(stream.as_ref(), || self.expand_dims(axis))
116    }
117
118    /// See [`expand_dims_axes()`].
119    pub fn expand_dims_axes(&self, axes: &[i32]) -> Result<Array> {
120        expand_dims_axes(self, axes)
121    }
122
123    /// Compatibility shim for [`expand_dims_axes`].
124    #[deprecated(
125        since = "0.26.0",
126        note = "use `with_stream` or `with_device` around `expand_dims_axes`"
127    )]
128    pub fn expand_dims_axes_device(
129        &self,
130        axes: &[i32],
131        stream: impl AsRef<Stream>,
132    ) -> Result<Array> {
133        crate::with_stream(stream.as_ref(), || self.expand_dims_axes(axes))
134    }
135
136    /// See [`flatten`].
137    pub fn flatten(
138        &self,
139        start_axis: impl Into<Option<i32>>,
140        end_axis: impl Into<Option<i32>>,
141    ) -> Result<Array> {
142        flatten(self, start_axis, end_axis)
143    }
144
145    /// Compatibility shim for [`flatten`].
146    #[deprecated(
147        since = "0.26.0",
148        note = "use `with_stream` or `with_device` around `flatten`"
149    )]
150    pub fn flatten_device(
151        &self,
152        start_axis: impl Into<Option<i32>>,
153        end_axis: impl Into<Option<i32>>,
154        stream: impl AsRef<Stream>,
155    ) -> Result<Array> {
156        crate::with_stream(stream.as_ref(), || self.flatten(start_axis, end_axis))
157    }
158
159    /// See [`reshape`].
160    pub fn reshape(&self, shape: &[i32]) -> Result<Array> {
161        reshape(self, shape)
162    }
163
164    /// Compatibility shim for [`reshape`].
165    #[deprecated(
166        since = "0.26.0",
167        note = "use `with_stream` or `with_device` around `reshape`"
168    )]
169    pub fn reshape_device(&self, shape: &[i32], stream: impl AsRef<Stream>) -> Result<Array> {
170        crate::with_stream(stream.as_ref(), || self.reshape(shape))
171    }
172
173    /// See [`squeeze_axes()`].
174    pub fn squeeze_axes(&self, axes: &[i32]) -> Result<Array> {
175        squeeze_axes(self, axes)
176    }
177
178    /// Compatibility shim for [`squeeze_axes`].
179    #[deprecated(
180        since = "0.26.0",
181        note = "use `with_stream` or `with_device` around `squeeze_axes`"
182    )]
183    pub fn squeeze_axes_device(&self, axes: &[i32], stream: impl AsRef<Stream>) -> Result<Array> {
184        crate::with_stream(stream.as_ref(), || self.squeeze_axes(axes))
185    }
186
187    /// See [`squeeze()`].
188    pub fn squeeze(&self) -> Result<Array> {
189        squeeze(self)
190    }
191
192    /// Compatibility shim for [`squeeze`].
193    #[deprecated(
194        since = "0.26.0",
195        note = "use `with_stream` or `with_device` around `squeeze`"
196    )]
197    pub fn squeeze_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
198        crate::with_stream(stream.as_ref(), || self.squeeze())
199    }
200
201    /// See [`as_strided`]
202    pub fn as_strided<'a>(
203        &'a self,
204        shape: impl IntoOption<&'a [i32]>,
205        strides: impl IntoOption<&'a [i64]>,
206        offset: impl Into<Option<usize>>,
207    ) -> Result<Array> {
208        as_strided(self, shape, strides, offset)
209    }
210
211    /// Compatibility shim for [`as_strided`].
212    #[deprecated(
213        since = "0.26.0",
214        note = "use `with_stream` or `with_device` around `as_strided`"
215    )]
216    pub fn as_strided_device<'a>(
217        &'a self,
218        shape: impl IntoOption<&'a [i32]>,
219        strides: impl IntoOption<&'a [i64]>,
220        offset: impl Into<Option<usize>>,
221        stream: impl AsRef<Stream>,
222    ) -> Result<Array> {
223        crate::with_stream(stream.as_ref(), || self.as_strided(shape, strides, offset))
224    }
225
226    /// See [`at_least_1d`]
227    pub fn at_least_1d(&self) -> Result<Array> {
228        at_least_1d(self)
229    }
230
231    /// Compatibility shim for [`at_least_1d`].
232    #[deprecated(
233        since = "0.26.0",
234        note = "use `with_stream` or `with_device` around `at_least_1d`"
235    )]
236    pub fn at_least_1d_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
237        crate::with_stream(stream.as_ref(), || self.at_least_1d())
238    }
239
240    /// See [`at_least_2d`]
241    pub fn at_least_2d(&self) -> Result<Array> {
242        at_least_2d(self)
243    }
244
245    /// Compatibility shim for [`at_least_2d`].
246    #[deprecated(
247        since = "0.26.0",
248        note = "use `with_stream` or `with_device` around `at_least_2d`"
249    )]
250    pub fn at_least_2d_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
251        crate::with_stream(stream.as_ref(), || self.at_least_2d())
252    }
253
254    /// See [`at_least_3d`]
255    pub fn at_least_3d(&self) -> Result<Array> {
256        at_least_3d(self)
257    }
258
259    /// Compatibility shim for [`at_least_3d`].
260    #[deprecated(
261        since = "0.26.0",
262        note = "use `with_stream` or `with_device` around `at_least_3d`"
263    )]
264    pub fn at_least_3d_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
265        crate::with_stream(stream.as_ref(), || self.at_least_3d())
266    }
267
268    /// See [`move_axis`]
269    pub fn move_axis(&self, src: i32, dst: i32) -> Result<Array> {
270        move_axis(self, src, dst)
271    }
272
273    /// Compatibility shim for [`move_axis`].
274    #[deprecated(
275        since = "0.26.0",
276        note = "use `with_stream` or `with_device` around `move_axis`"
277    )]
278    pub fn move_axis_device(
279        &self,
280        src: i32,
281        dst: i32,
282        stream: impl AsRef<Stream>,
283    ) -> Result<Array> {
284        crate::with_stream(stream.as_ref(), || self.move_axis(src, dst))
285    }
286
287    /// See [`split_at_indices`].
288    pub fn split_at_indices(
289        &self,
290        indices: &[i32],
291        axis: impl Into<Option<i32>>,
292    ) -> Result<Vec<Array>> {
293        split_at_indices(self, indices, axis)
294    }
295
296    /// Compatibility alias for [`Array::split_at_indices`].
297    #[deprecated(since = "0.26.0", note = "renamed to `split_at_indices`")]
298    pub fn split_axis(&self, indices: &[i32], axis: impl Into<Option<i32>>) -> Result<Vec<Array>> {
299        self.split_at_indices(indices, axis)
300    }
301
302    /// Compatibility shim for [`split_axis`].
303    #[deprecated(
304        since = "0.26.0",
305        note = "use `with_stream` or `with_device` around `split_at_indices`"
306    )]
307    pub fn split_axis_device(
308        &self,
309        indices: &[i32],
310        axis: impl Into<Option<i32>>,
311        stream: impl AsRef<Stream>,
312    ) -> Result<Vec<Array>> {
313        crate::with_stream(stream.as_ref(), || self.split_at_indices(indices, axis))
314    }
315
316    /// See [`split_equal`].
317    pub fn split_equal(&self, num_parts: i32, axis: impl Into<Option<i32>>) -> Result<Vec<Array>> {
318        split_equal(self, num_parts, axis)
319    }
320
321    /// Compatibility alias for [`Array::split_equal`].
322    #[deprecated(since = "0.26.0", note = "renamed to `split_equal`")]
323    pub fn split(&self, num_parts: i32, axis: impl Into<Option<i32>>) -> Result<Vec<Array>> {
324        self.split_equal(num_parts, axis)
325    }
326
327    /// Compatibility shim for [`split`].
328    #[deprecated(
329        since = "0.26.0",
330        note = "use `with_stream` or `with_device` around `split_equal`"
331    )]
332    pub fn split_device(
333        &self,
334        num_parts: i32,
335        axis: impl Into<Option<i32>>,
336        stream: impl AsRef<Stream>,
337    ) -> Result<Vec<Array>> {
338        crate::with_stream(stream.as_ref(), || self.split_equal(num_parts, axis))
339    }
340
341    /// See [`swap_axes`]
342    pub fn swap_axes(&self, axis1: i32, axis2: i32) -> Result<Array> {
343        swap_axes(self, axis1, axis2)
344    }
345
346    /// Compatibility shim for [`swap_axes`].
347    #[deprecated(
348        since = "0.26.0",
349        note = "use `with_stream` or `with_device` around `swap_axes`"
350    )]
351    pub fn swap_axes_device(
352        &self,
353        axis1: i32,
354        axis2: i32,
355        stream: impl AsRef<Stream>,
356    ) -> Result<Array> {
357        crate::with_stream(stream.as_ref(), || self.swap_axes(axis1, axis2))
358    }
359
360    /// See [`transpose_axes`]
361    pub fn transpose_axes(&self, axes: &[i32]) -> Result<Array> {
362        transpose_axes(self, axes)
363    }
364
365    /// Compatibility shim for [`transpose_axes`].
366    #[deprecated(
367        since = "0.26.0",
368        note = "use `with_stream` or `with_device` around `transpose_axes`"
369    )]
370    pub fn transpose_axes_device(&self, axes: &[i32], stream: impl AsRef<Stream>) -> Result<Array> {
371        crate::with_stream(stream.as_ref(), || self.transpose_axes(axes))
372    }
373
374    /// See [`transpose`]
375    pub fn transpose(&self) -> Result<Array> {
376        transpose(self)
377    }
378
379    /// Compatibility shim for [`transpose`].
380    #[deprecated(
381        since = "0.26.0",
382        note = "use `with_stream` or `with_device` around `transpose`"
383    )]
384    pub fn transpose_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
385        crate::with_stream(stream.as_ref(), || self.transpose())
386    }
387
388    /// [`transpose_axes`] and unwrap the result.
389    pub fn t(&self) -> Array {
390        self.transpose().unwrap()
391    }
392}
393fn resolve_strides(
394    shape: &[i32],
395    strides: Option<&[i64]>,
396) -> SmallVec<[i64; DEFAULT_STACK_VEC_LEN]> {
397    match strides {
398        Some(strides) => SmallVec::from_slice(strides),
399        None => {
400            let result = shape
401                .iter()
402                .rev()
403                .scan(1, |acc, &dim| {
404                    let result = *acc;
405                    *acc *= dim as i64;
406                    Some(result)
407                })
408                .collect::<SmallVec<[i64; DEFAULT_STACK_VEC_LEN]>>();
409            result.into_iter().rev().collect()
410        }
411    }
412}
413
414/// Broadcast a vector of arrays against one another. Returns an error if the shapes are
415/// broadcastable.
416///
417/// # Params
418///
419/// - `arrays`: The arrays to broadcast.
420pub fn broadcast_arrays(arrays: &[impl AsRef<Array>]) -> Result<Vec<Array>> {
421    let stream = Stream::thread_local_or_default();
422    let c_vec = VectorArray::try_from_iter(arrays.iter())?;
423    Vec::<Array>::try_from_op(|res| unsafe {
424        mlx_sys::mlx_broadcast_arrays(res, c_vec.as_ptr(), stream.as_ref().as_ptr())
425    })
426}
427
428/// Compatibility shim for [`broadcast_arrays`].
429#[generate_macro(customize(forwarding_shim = true))]
430#[deprecated(
431    since = "0.26.0",
432    note = "use `with_stream` or `with_device` around `broadcast_arrays`"
433)]
434pub fn broadcast_arrays_device(
435    arrays: &[impl AsRef<Array>],
436    #[optional] stream: impl AsRef<Stream>,
437) -> Result<Vec<Array>> {
438    crate::with_stream(stream.as_ref(), || broadcast_arrays(arrays))
439}
440
441/// Create a view into the array with the given shape and strides.
442///
443/// # Example
444///
445/// ```rust
446/// use mlx_rs::{Array, ops::*};
447///
448/// let x = Array::from_iter(0..10, &[10]);
449/// let y = as_strided(&x, &[3, 3], &[1, 1], 0);
450/// ```
451pub fn as_strided<'a>(
452    a: impl AsRef<Array>,
453    shape: impl IntoOption<&'a [i32]>,
454    strides: impl IntoOption<&'a [i64]>,
455    offset: impl Into<Option<usize>>,
456) -> Result<Array> {
457    let stream = Stream::thread_local_or_default();
458    let a = a.as_ref();
459    let shape = shape.into_option().unwrap_or(a.shape());
460    let resolved_strides = resolve_strides(shape, strides.into_option());
461    let offset = offset.into().unwrap_or(0);
462
463    Array::try_from_op(|res| unsafe {
464        mlx_sys::mlx_as_strided(
465            res,
466            a.as_ptr(),
467            shape.as_ptr(),
468            shape.len(),
469            resolved_strides.as_ptr(),
470            resolved_strides.len(),
471            offset,
472            stream.as_ref().as_ptr(),
473        )
474    })
475}
476
477/// Compatibility shim for [`as_strided`].
478#[generate_macro(customize(forwarding_shim = true))]
479#[deprecated(
480    since = "0.26.0",
481    note = "use `with_stream` or `with_device` around `as_strided`"
482)]
483pub fn as_strided_device<'a>(
484    a: impl AsRef<Array>,
485    #[optional] shape: impl IntoOption<&'a [i32]>,
486    #[optional] strides: impl IntoOption<&'a [i64]>,
487    #[optional] offset: impl Into<Option<usize>>,
488    #[optional] stream: impl AsRef<Stream>,
489) -> Result<Array> {
490    crate::with_stream(stream.as_ref(), || as_strided(a, shape, strides, offset))
491}
492
493/// Broadcast an array to the given shape. Returns an error if the shapes are not broadcastable.
494///
495/// # Params
496///
497/// - `a`: The input array.
498/// - `shape`: The shape to broadcast to.
499///
500/// # Example
501///
502/// ```rust
503/// use mlx_rs::{Array, ops::*};
504///
505/// let x = Array::from_f32(2.3);
506/// let result = broadcast_to(&x, &[1, 1]);
507/// ```
508pub fn broadcast_to(a: impl AsRef<Array>, shape: &[i32]) -> Result<Array> {
509    let stream = Stream::thread_local_or_default();
510    Array::try_from_op(|res| unsafe {
511        mlx_sys::mlx_broadcast_to(
512            res,
513            a.as_ref().as_ptr(),
514            shape.as_ptr(),
515            shape.len(),
516            stream.as_ref().as_ptr(),
517        )
518    })
519}
520
521/// Compatibility shim for [`broadcast_to`].
522#[generate_macro(customize(forwarding_shim = true))]
523#[deprecated(
524    since = "0.26.0",
525    note = "use `with_stream` or `with_device` around `broadcast_to`"
526)]
527pub fn broadcast_to_device(
528    a: impl AsRef<Array>,
529    shape: &[i32],
530    #[optional] stream: impl AsRef<Stream>,
531) -> Result<Array> {
532    crate::with_stream(stream.as_ref(), || broadcast_to(a, shape))
533}
534
535/// Concatenate the arrays along the given axis. Returns an error if the shapes are invalid.
536///
537/// # Params
538///
539/// - `arrays`: The arrays to concatenate.
540/// - `axis`: The axis to concatenate along.
541///
542/// # Example
543///
544/// ```rust
545/// use mlx_rs::{Array, ops::*};
546///
547/// let x = Array::from_iter(0..4, &[2, 2]);
548/// let y = Array::from_iter(4..8, &[2, 2]);
549/// let result = concatenate(&[x, y], 0);
550/// ```
551pub fn concatenate(arrays: &[impl AsRef<Array>], axis: i32) -> Result<Array> {
552    let stream = Stream::thread_local_or_default();
553    let c_arrays = VectorArray::try_from_iter(arrays.iter())?;
554    Array::try_from_op(|res| unsafe {
555        mlx_sys::mlx_concatenate_axis(res, c_arrays.as_ptr(), axis, stream.as_ref().as_ptr())
556    })
557}
558
559/// Compatibility alias for [`concatenate`].
560#[deprecated(since = "0.26.0", note = "renamed to `concatenate`")]
561pub fn concatenate_axis(arrays: &[impl AsRef<Array>], axis: i32) -> Result<Array> {
562    concatenate(arrays, axis)
563}
564
565/// Compatibility shim for [`concatenate`].
566#[generate_macro(customize(forwarding_shim = true))]
567#[deprecated(
568    since = "0.26.0",
569    note = "use `with_stream` or `with_device` around `concatenate`"
570)]
571pub fn concatenate_axis_device(
572    arrays: &[impl AsRef<Array>],
573    axis: i32,
574    #[optional] stream: impl AsRef<Stream>,
575) -> Result<Array> {
576    crate::with_stream(stream.as_ref(), || concatenate(arrays, axis))
577}
578
579/// Flatten the arrays and concatenate them. Use [`concatenate`] to concatenate along an axis.
580pub fn concatenate_flat(arrays: &[impl AsRef<Array>]) -> Result<Array> {
581    let stream = Stream::thread_local_or_default();
582    let c_arrays = VectorArray::try_from_iter(arrays.iter())?;
583    Array::try_from_op(|res| unsafe {
584        mlx_sys::mlx_concatenate(res, c_arrays.as_ptr(), stream.as_ref().as_ptr())
585    })
586}
587
588/// Compatibility shim preserving the old flattening behavior of `concatenate`.
589#[generate_macro(customize(forwarding_shim = true))]
590#[deprecated(
591    since = "0.26.0",
592    note = "the old `concatenate` flattened inputs; use `concatenate_flat`"
593)]
594pub fn concatenate_device(
595    arrays: &[impl AsRef<Array>],
596    #[optional] stream: impl AsRef<Stream>,
597) -> Result<Array> {
598    crate::with_stream(stream.as_ref(), || concatenate_flat(arrays))
599}
600
601/// Add a size one dimension at the given axis, returns an error if the axes are invalid.
602///
603/// # Params
604///
605/// - `a`: The input array.
606/// - `axes`: The index of the inserted dimensions.
607///
608/// # Example
609///
610/// ```rust
611/// use mlx_rs::{Array, ops::*};
612///
613/// let x = Array::zeros::<i32>(&[2, 2]).unwrap();
614/// let result = expand_dims_axes(&x, &[0]);
615/// ```
616pub fn expand_dims_axes(a: impl AsRef<Array>, axes: &[i32]) -> Result<Array> {
617    let stream = Stream::thread_local_or_default();
618    Array::try_from_op(|res| unsafe {
619        mlx_sys::mlx_expand_dims_axes(
620            res,
621            a.as_ref().as_ptr(),
622            axes.as_ptr(),
623            axes.len(),
624            stream.as_ref().as_ptr(),
625        )
626    })
627}
628
629/// Compatibility shim for [`expand_dims_axes`].
630#[generate_macro(customize(forwarding_shim = true))]
631#[deprecated(
632    since = "0.26.0",
633    note = "use `with_stream` or `with_device` around `expand_dims_axes`"
634)]
635pub fn expand_dims_axes_device(
636    a: impl AsRef<Array>,
637    axes: &[i32],
638    #[optional] stream: impl AsRef<Stream>,
639) -> Result<Array> {
640    crate::with_stream(stream.as_ref(), || expand_dims_axes(a, axes))
641}
642
643/// Similar to [`expand_dims_axes`], but only takes a single axis.
644pub fn expand_dims(a: impl AsRef<Array>, axis: i32) -> Result<Array> {
645    let stream = Stream::thread_local_or_default();
646    Array::try_from_op(|res| unsafe {
647        mlx_sys::mlx_expand_dims(res, a.as_ref().as_ptr(), axis, stream.as_ref().as_ptr())
648    })
649}
650
651/// Compatibility shim for [`expand_dims`].
652#[generate_macro(customize(forwarding_shim = true))]
653#[deprecated(
654    since = "0.26.0",
655    note = "use `with_stream` or `with_device` around `expand_dims`"
656)]
657pub fn expand_dims_device(
658    a: impl AsRef<Array>,
659    axis: i32,
660    #[optional] stream: impl AsRef<Stream>,
661) -> Result<Array> {
662    crate::with_stream(stream.as_ref(), || expand_dims(a, axis))
663}
664
665/// Flatten an array. Returns an error if the axes are invalid.
666///
667/// The axes flattened will be between `start_axis` and `end_axis`, inclusive. Negative axes are
668/// supported. After converting negative axis to positive, axes outside the valid range will be
669/// clamped to a valid value, `start_axis` to `0` and `end_axis` to `ndim - 1`.
670///
671/// # Params
672///
673/// - `a`: The input array.
674/// - `start_axis`: The first axis to flatten. Default is `0` if not provided.
675/// - `end_axis`: The last axis to flatten. Default is `-1` if not provided.
676///
677/// # Example
678///
679/// ```rust
680/// use mlx_rs::{Array, ops::*};
681///
682/// let x = Array::zeros::<i32>(&[2, 2, 2]).unwrap();
683/// let y = flatten(&x, None, None);
684/// ```
685pub fn flatten(
686    a: impl AsRef<Array>,
687    start_axis: impl Into<Option<i32>>,
688    end_axis: impl Into<Option<i32>>,
689) -> Result<Array> {
690    let stream = Stream::thread_local_or_default();
691    let start_axis = start_axis.into().unwrap_or(0);
692    let end_axis = end_axis.into().unwrap_or(-1);
693
694    Array::try_from_op(|res| unsafe {
695        mlx_sys::mlx_flatten(
696            res,
697            a.as_ref().as_ptr(),
698            start_axis,
699            end_axis,
700            stream.as_ref().as_ptr(),
701        )
702    })
703}
704
705/// Compatibility shim for [`flatten`].
706#[generate_macro(customize(forwarding_shim = true))]
707#[deprecated(
708    since = "0.26.0",
709    note = "use `with_stream` or `with_device` around `flatten`"
710)]
711pub fn flatten_device(
712    a: impl AsRef<Array>,
713    #[optional] start_axis: impl Into<Option<i32>>,
714    #[optional] end_axis: impl Into<Option<i32>>,
715    #[optional] stream: impl AsRef<Stream>,
716) -> Result<Array> {
717    crate::with_stream(stream.as_ref(), || flatten(a, start_axis, end_axis))
718}
719
720/// Unflatten an axis of an array to a shape.
721///
722/// # Params
723///
724/// - `a`: input array
725/// - `axis`: axis to unflatten
726/// - `shape`: shape to unflatten into
727pub fn unflatten(a: impl AsRef<Array>, axis: i32, shape: &[i32]) -> Result<Array> {
728    let stream = Stream::thread_local_or_default();
729    Array::try_from_op(|res| unsafe {
730        mlx_sys::mlx_unflatten(
731            res,
732            a.as_ref().as_ptr(),
733            axis,
734            shape.as_ptr(),
735            shape.len(),
736            stream.as_ref().as_ptr(),
737        )
738    })
739}
740
741/// Compatibility shim for [`unflatten`].
742#[generate_macro(customize(forwarding_shim = true))]
743#[deprecated(
744    since = "0.26.0",
745    note = "use `with_stream` or `with_device` around `unflatten`"
746)]
747pub fn unflatten_device(
748    a: impl AsRef<Array>,
749    axis: i32,
750    shape: &[i32],
751    #[optional] stream: impl AsRef<Stream>,
752) -> Result<Array> {
753    crate::with_stream(stream.as_ref(), || unflatten(a, axis, shape))
754}
755
756/// Reshape an array while preserving the size. Returns an error if the new shape is invalid.
757///
758/// # Params
759///
760/// - `a`: The input array.
761/// - `shape`: New shape.
762///
763/// # Example
764///
765/// ```rust
766/// use mlx_rs::{Array, ops::*};
767///
768/// let x = Array::zeros::<i32>(&[2, 2]).unwrap();
769/// let result = reshape(&x, &[4]);
770/// ```
771pub fn reshape(a: impl AsRef<Array>, shape: &[i32]) -> Result<Array> {
772    let stream = Stream::thread_local_or_default();
773    Array::try_from_op(|res| unsafe {
774        mlx_sys::mlx_reshape(
775            res,
776            a.as_ref().as_ptr(),
777            shape.as_ptr(),
778            shape.len(),
779            stream.as_ref().as_ptr(),
780        )
781    })
782}
783
784/// Compatibility shim for [`reshape`].
785#[generate_macro(customize(forwarding_shim = true))]
786#[deprecated(
787    since = "0.26.0",
788    note = "use `with_stream` or `with_device` around `reshape`"
789)]
790pub fn reshape_device(
791    a: impl AsRef<Array>,
792    shape: &[i32],
793    #[optional] stream: impl AsRef<Stream>,
794) -> Result<Array> {
795    crate::with_stream(stream.as_ref(), || reshape(a, shape))
796}
797
798/// Remove length one axes from an array. Returns an error if the axes are invalid.
799///
800/// # Params
801///
802/// - `a`: The input array.
803/// - `axes`: Axes to remove. If `None`, all length one axes will be removed.
804///
805/// # Example
806///
807/// ```rust
808/// use mlx_rs::{Array, ops::*};
809///
810/// let x = Array::zeros::<i32>(&[1, 2, 1, 3]).unwrap();
811/// let result = squeeze(&x);
812/// ```
813pub fn squeeze_axes(a: impl AsRef<Array>, axes: &[i32]) -> Result<Array> {
814    let stream = Stream::thread_local_or_default();
815    let a = a.as_ref();
816    Array::try_from_op(|res| unsafe {
817        mlx_sys::mlx_squeeze_axes(
818            res,
819            a.as_ptr(),
820            axes.as_ptr(),
821            axes.len(),
822            stream.as_ref().as_ptr(),
823        )
824    })
825}
826
827/// Compatibility shim for [`squeeze_axes`].
828#[generate_macro(customize(forwarding_shim = true))]
829#[deprecated(
830    since = "0.26.0",
831    note = "use `with_stream` or `with_device` around `squeeze_axes`"
832)]
833pub fn squeeze_axes_device(
834    a: impl AsRef<Array>,
835    axes: &[i32],
836    #[optional] stream: impl AsRef<Stream>,
837) -> Result<Array> {
838    crate::with_stream(stream.as_ref(), || squeeze_axes(a, axes))
839}
840
841/// Similar to [`squeeze_axes`], but removes all length one axes.
842pub fn squeeze(a: impl AsRef<Array>) -> Result<Array> {
843    let stream = Stream::thread_local_or_default();
844    let a = a.as_ref();
845    Array::try_from_op(|res| unsafe {
846        mlx_sys::mlx_squeeze(res, a.as_ptr(), stream.as_ref().as_ptr())
847    })
848}
849
850/// Compatibility shim for [`squeeze`].
851#[generate_macro(customize(forwarding_shim = true))]
852#[deprecated(
853    since = "0.26.0",
854    note = "use `with_stream` or `with_device` around `squeeze`"
855)]
856pub fn squeeze_device(
857    a: impl AsRef<Array>,
858    #[optional] stream: impl AsRef<Stream>,
859) -> Result<Array> {
860    crate::with_stream(stream.as_ref(), || squeeze(a))
861}
862
863/// Convert array to have at least one dimension.
864///
865/// # Params
866///
867/// - `a`: The input array.
868///
869/// # Example
870///
871/// ```rust
872/// use mlx_rs::{Array, ops::*};
873///
874/// let x = Array::from_int(1);
875/// let out = at_least_1d(&x);
876/// ```
877pub fn at_least_1d(a: impl AsRef<Array>) -> Result<Array> {
878    let stream = Stream::thread_local_or_default();
879    Array::try_from_op(|res| unsafe {
880        mlx_sys::mlx_atleast_1d(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
881    })
882}
883
884/// Compatibility shim for [`at_least_1d`].
885#[generate_macro(customize(forwarding_shim = true))]
886#[deprecated(
887    since = "0.26.0",
888    note = "use `with_stream` or `with_device` around `at_least_1d`"
889)]
890pub fn at_least_1d_device(
891    a: impl AsRef<Array>,
892    #[optional] stream: impl AsRef<Stream>,
893) -> Result<Array> {
894    crate::with_stream(stream.as_ref(), || at_least_1d(a))
895}
896
897/// Convert array to have at least two dimensions.
898///
899/// # Params
900///
901/// - `a`: The input array.
902///
903/// # Example
904///
905/// ```rust
906/// use mlx_rs::{Array, ops::*};
907///
908/// let x = Array::from_int(1);
909/// let out = at_least_2d(&x);
910/// ```
911pub fn at_least_2d(a: impl AsRef<Array>) -> Result<Array> {
912    let stream = Stream::thread_local_or_default();
913    Array::try_from_op(|res| unsafe {
914        mlx_sys::mlx_atleast_2d(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
915    })
916}
917
918/// Compatibility shim for [`at_least_2d`].
919#[generate_macro(customize(forwarding_shim = true))]
920#[deprecated(
921    since = "0.26.0",
922    note = "use `with_stream` or `with_device` around `at_least_2d`"
923)]
924pub fn at_least_2d_device(
925    a: impl AsRef<Array>,
926    #[optional] stream: impl AsRef<Stream>,
927) -> Result<Array> {
928    crate::with_stream(stream.as_ref(), || at_least_2d(a))
929}
930
931/// Convert array to have at least three dimensions.
932///
933/// # Params
934///
935/// - `a`: The input array.
936///
937/// # Example
938///
939/// ```rust
940/// use mlx_rs::{Array, ops::*};
941///
942/// let x = Array::from_int(1);
943/// let out = at_least_3d(&x);
944/// ```
945pub fn at_least_3d(a: impl AsRef<Array>) -> Result<Array> {
946    let stream = Stream::thread_local_or_default();
947    Array::try_from_op(|res| unsafe {
948        mlx_sys::mlx_atleast_3d(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
949    })
950}
951
952/// Compatibility shim for [`at_least_3d`].
953#[generate_macro(customize(forwarding_shim = true))]
954#[deprecated(
955    since = "0.26.0",
956    note = "use `with_stream` or `with_device` around `at_least_3d`"
957)]
958pub fn at_least_3d_device(
959    a: impl AsRef<Array>,
960    #[optional] stream: impl AsRef<Stream>,
961) -> Result<Array> {
962    crate::with_stream(stream.as_ref(), || at_least_3d(a))
963}
964
965/// Move an axis to a new position. Returns an error if the axes are invalid.
966///
967/// # Params
968///
969/// - `a`: The input array.
970/// - `src`: Specifies the source axis.
971/// - `dst`: Specifies the destination axis.
972///
973/// # Example
974///
975/// ```rust
976/// use mlx_rs::{Array, ops::*};
977///
978/// let a = Array::zeros::<i32>(&[2, 3, 4]).unwrap();
979/// let result = move_axis(&a, 0, 2);
980/// ```
981pub fn move_axis(a: impl AsRef<Array>, src: i32, dst: i32) -> Result<Array> {
982    let stream = Stream::thread_local_or_default();
983    Array::try_from_op(|res| unsafe {
984        mlx_sys::mlx_moveaxis(res, a.as_ref().as_ptr(), src, dst, stream.as_ref().as_ptr())
985    })
986}
987
988/// Compatibility shim for [`move_axis`].
989#[generate_macro(customize(forwarding_shim = true))]
990#[deprecated(
991    since = "0.26.0",
992    note = "use `with_stream` or `with_device` around `move_axis`"
993)]
994pub fn move_axis_device(
995    a: impl AsRef<Array>,
996    src: i32,
997    dst: i32,
998    #[optional] stream: impl AsRef<Stream>,
999) -> Result<Array> {
1000    crate::with_stream(stream.as_ref(), || move_axis(a, src, dst))
1001}
1002
1003/// Split an array along a given axis. Returns an error if the indices are invalid.
1004///
1005/// # Params
1006///
1007/// - `a`: The input array.
1008/// - `indices`: The indices to split at.
1009/// - `axis`: The axis to split along. Default is `0` if not provided.
1010///
1011/// # Example
1012///
1013/// ```rust
1014/// use mlx_rs::{Array, ops::*};
1015///
1016/// let a = Array::from_iter(0..10, &[10]);
1017/// let result = split_at_indices(&a, &[3, 7], 0);
1018/// ```
1019pub fn split_at_indices(
1020    a: impl AsRef<Array>,
1021    indices: &[i32],
1022    axis: impl Into<Option<i32>>,
1023) -> Result<Vec<Array>> {
1024    let stream = Stream::thread_local_or_default();
1025    let axis = axis.into().unwrap_or(0);
1026    Vec::<Array>::try_from_op(|res| unsafe {
1027        mlx_sys::mlx_split_sections(
1028            res,
1029            a.as_ref().as_ptr(),
1030            indices.as_ptr(),
1031            indices.len(),
1032            axis,
1033            stream.as_ref().as_ptr(),
1034        )
1035    })
1036}
1037
1038/// Compatibility alias for [`split_at_indices`].
1039#[deprecated(since = "0.26.0", note = "renamed to `split_at_indices`")]
1040pub fn split_sections(
1041    a: impl AsRef<Array>,
1042    indices: &[i32],
1043    axis: impl Into<Option<i32>>,
1044) -> Result<Vec<Array>> {
1045    split_at_indices(a, indices, axis)
1046}
1047
1048/// Compatibility shim for [`split_at_indices`].
1049#[generate_macro(customize(forwarding_shim = true))]
1050#[deprecated(
1051    since = "0.26.0",
1052    note = "use `with_stream` or `with_device` around `split_at_indices`"
1053)]
1054pub fn split_sections_device(
1055    a: impl AsRef<Array>,
1056    indices: &[i32],
1057    #[optional] axis: impl Into<Option<i32>>,
1058    #[optional] stream: impl AsRef<Stream>,
1059) -> Result<Vec<Array>> {
1060    crate::with_stream(stream.as_ref(), || split_at_indices(a, indices, axis))
1061}
1062
1063/// Split an array into equal parts along a given axis. Returns an error if the array cannot be
1064/// split into equal parts.
1065///
1066/// # Params
1067///
1068/// - `a`: The input array.
1069/// - `num_parts`: The number of parts to split into.
1070/// - `axis`: The axis to split along. Default is `0` if not provided.
1071///
1072/// # Example
1073///
1074/// ```rust
1075/// use mlx_rs::{Array, ops::*};
1076///
1077/// let a = Array::from_iter(0..10, &[10]);
1078/// let result = split_equal(&a, 2, 0);
1079/// ```
1080pub fn split_equal(
1081    a: impl AsRef<Array>,
1082    num_parts: i32,
1083    axis: impl Into<Option<i32>>,
1084) -> Result<Vec<Array>> {
1085    let stream = Stream::thread_local_or_default();
1086    let axis = axis.into().unwrap_or(0);
1087    Vec::<Array>::try_from_op(|res| unsafe {
1088        mlx_sys::mlx_split(
1089            res,
1090            a.as_ref().as_ptr(),
1091            num_parts,
1092            axis,
1093            stream.as_ref().as_ptr(),
1094        )
1095    })
1096}
1097
1098/// Compatibility alias for [`split_equal`].
1099#[deprecated(since = "0.26.0", note = "renamed to `split_equal`")]
1100pub fn split(
1101    a: impl AsRef<Array>,
1102    num_parts: i32,
1103    axis: impl Into<Option<i32>>,
1104) -> Result<Vec<Array>> {
1105    split_equal(a, num_parts, axis)
1106}
1107
1108/// Compatibility shim for [`split_equal`].
1109#[generate_macro(customize(forwarding_shim = true))]
1110#[deprecated(
1111    since = "0.26.0",
1112    note = "use `with_stream` or `with_device` around `split_equal`"
1113)]
1114pub fn split_device(
1115    a: impl AsRef<Array>,
1116    num_parts: i32,
1117    #[optional] axis: impl Into<Option<i32>>,
1118    #[optional] stream: impl AsRef<Stream>,
1119) -> Result<Vec<Array>> {
1120    crate::with_stream(stream.as_ref(), || split_equal(a, num_parts, axis))
1121}
1122
1123/// Number of padding values to add to the edges of each axis.
1124#[derive(Debug)]
1125pub enum PadWidth<'a> {
1126    /// (before, after) values for all axes.
1127    Same((i32, i32)),
1128
1129    /// List of (before, after) values for each axis.
1130    Widths(&'a [(i32, i32)]),
1131}
1132
1133impl From<i32> for PadWidth<'_> {
1134    fn from(width: i32) -> Self {
1135        PadWidth::Same((width, width))
1136    }
1137}
1138
1139impl From<(i32, i32)> for PadWidth<'_> {
1140    fn from(width: (i32, i32)) -> Self {
1141        PadWidth::Same(width)
1142    }
1143}
1144
1145impl<'a> From<&'a [(i32, i32)]> for PadWidth<'a> {
1146    fn from(widths: &'a [(i32, i32)]) -> Self {
1147        PadWidth::Widths(widths)
1148    }
1149}
1150
1151impl<'a, const N: usize> From<&'a [(i32, i32); N]> for PadWidth<'a> {
1152    fn from(widths: &'a [(i32, i32); N]) -> Self {
1153        PadWidth::Widths(widths)
1154    }
1155}
1156
1157impl PadWidth<'_> {
1158    fn low_pads(&self, ndim: usize) -> SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> {
1159        match self {
1160            PadWidth::Same((low, _high)) => (0..ndim).map(|_| *low).collect(),
1161            PadWidth::Widths(widths) => widths.iter().map(|(low, _high)| *low).collect(),
1162        }
1163    }
1164    fn high_pads(&self, ndim: usize) -> SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> {
1165        match self {
1166            PadWidth::Same((_low, high)) => (0..ndim).map(|_| *high).collect(),
1167            PadWidth::Widths(widths) => widths.iter().map(|(_low, high)| *high).collect(),
1168        }
1169    }
1170}
1171
1172/// The padding mode.
1173#[derive(Debug)]
1174pub enum PadMode {
1175    /// Pad with a constant value.
1176    Constant,
1177
1178    /// Pad with the edge value.
1179    Edge,
1180}
1181
1182impl PadMode {
1183    unsafe fn as_c_str(&self) -> *const i8 {
1184        static CONSTANT: &[u8] = b"constant\0";
1185        static EDGE: &[u8] = b"edge\0";
1186
1187        match self {
1188            PadMode::Constant => CONSTANT.as_ptr() as *const _,
1189            PadMode::Edge => EDGE.as_ptr() as *const _,
1190        }
1191    }
1192}
1193
1194/// Pad an array with a constant value. Returns an error if the width is invalid.
1195///
1196/// # Params
1197///
1198/// - `a`: The input array.
1199/// - `width`: Number of padded values to add to the edges of each axis:`((before_1, after_1),
1200///   (before_2, after_2), ..., (before_N, after_N))`. If a single pair of integers is passed then
1201///   `(before_i, after_i)` are all the same. If a single integer or tuple with a single integer is
1202///   passed then all axes are extended by the same number on each side.
1203/// - `value`: The value to pad the array with. Default is `0` if not provided.
1204/// - `mode`: The padding mode. Default is `PadMode::Constant` if not provided.
1205///
1206/// # Example
1207///
1208/// ```rust
1209/// use mlx_rs::{Array, ops::*};
1210///
1211/// let a = Array::from_iter(0..4, &[2, 2]);
1212/// let result = pad(&a, 1, Array::from_int(0), None);
1213/// ```
1214pub fn pad<'a>(
1215    a: impl AsRef<Array>,
1216    width: impl Into<PadWidth<'a>>,
1217    value: impl Into<Option<Array>>,
1218    mode: impl Into<Option<PadMode>>,
1219) -> Result<Array> {
1220    let stream = Stream::thread_local_or_default();
1221    let a = a.as_ref();
1222    let width = width.into();
1223    let ndim = a.ndim();
1224    let axes: SmallVec<[i32; DEFAULT_STACK_VEC_LEN]> = (0..ndim).map(|i| i as i32).collect();
1225    let low_pads = width.low_pads(ndim);
1226    let high_pads = width.high_pads(ndim);
1227    let value = value
1228        .into()
1229        .map(Ok)
1230        .unwrap_or_else(|| Array::from_int(0).as_dtype(a.dtype()))?;
1231    let mode = mode.into().unwrap_or(PadMode::Constant);
1232
1233    Array::try_from_op(|res| unsafe {
1234        mlx_sys::mlx_pad(
1235            res,
1236            a.as_ptr(),
1237            axes.as_ptr(),
1238            axes.len(),
1239            low_pads.as_ptr(),
1240            low_pads.len(),
1241            high_pads.as_ptr(),
1242            high_pads.len(),
1243            value.as_ptr(),
1244            mode.as_c_str(),
1245            stream.as_ref().as_ptr(),
1246        )
1247    })
1248}
1249
1250/// Compatibility shim for [`pad`].
1251#[generate_macro(customize(forwarding_shim = true))]
1252#[deprecated(
1253    since = "0.26.0",
1254    note = "use `with_stream` or `with_device` around `pad`"
1255)]
1256pub fn pad_device<'a>(
1257    a: impl AsRef<Array>,
1258    #[optional] width: impl Into<PadWidth<'a>>,
1259    #[optional] value: impl Into<Option<Array>>,
1260    #[optional] mode: impl Into<Option<PadMode>>,
1261    #[optional] stream: impl AsRef<Stream>,
1262) -> Result<Array> {
1263    crate::with_stream(stream.as_ref(), || pad(a, width, value, mode))
1264}
1265
1266/// Stacks the arrays along a new axis. Returns an error if the arguments are invalid.
1267///
1268/// # Params
1269///
1270/// - `arrays`: The input arrays.
1271/// - `axis`: The axis in the result array along which the input arrays are stacked.
1272///
1273/// # Example
1274///
1275/// ```rust
1276/// use mlx_rs::{Array, ops::*};
1277///
1278/// let a = Array::from_iter(0..4, &[2, 2]);
1279/// let b = Array::from_iter(4..8, &[2, 2]);
1280/// let result = stack(&[&a, &b], 0);
1281/// ```
1282pub fn stack(arrays: &[impl AsRef<Array>], axis: i32) -> Result<Array> {
1283    let stream = Stream::thread_local_or_default();
1284    let c_vec = VectorArray::try_from_iter(arrays.iter())?;
1285    Array::try_from_op(|res| unsafe {
1286        mlx_sys::mlx_stack_axis(res, c_vec.as_ptr(), axis, stream.as_ref().as_ptr())
1287    })
1288}
1289
1290/// Compatibility alias for [`stack`].
1291#[deprecated(since = "0.26.0", note = "renamed to `stack`")]
1292pub fn stack_axis(arrays: &[impl AsRef<Array>], axis: i32) -> Result<Array> {
1293    stack(arrays, axis)
1294}
1295
1296/// Compatibility shim for [`stack`].
1297#[generate_macro(customize(forwarding_shim = true))]
1298#[deprecated(
1299    since = "0.26.0",
1300    note = "use `with_stream` or `with_device` around `stack`"
1301)]
1302pub fn stack_axis_device(
1303    arrays: &[impl AsRef<Array>],
1304    axis: i32,
1305    #[optional] stream: impl AsRef<Stream>,
1306) -> Result<Array> {
1307    crate::with_stream(stream.as_ref(), || stack(arrays, axis))
1308}
1309
1310/// Compatibility shim preserving the old axis-zero behavior of `stack`.
1311#[generate_macro(customize(forwarding_shim = true))]
1312#[deprecated(
1313    since = "0.26.0",
1314    note = "the old `stack` used axis 0; use `stack(arrays, 0)`"
1315)]
1316pub fn stack_device(
1317    arrays: &[impl AsRef<Array>],
1318    #[optional] stream: impl AsRef<Stream>,
1319) -> Result<Array> {
1320    crate::with_stream(stream.as_ref(), || stack(arrays, 0))
1321}
1322
1323/// Swap two axes of an array. Returns an error if the axes are invalid.
1324///
1325/// # Params
1326///
1327/// - `a`: The input array.
1328/// - `axis1`: The first axis.
1329/// - `axis2`: The second axis.
1330///
1331/// # Example
1332///
1333/// ```rust
1334/// use mlx_rs::{Array, ops::*};
1335///
1336/// let a = Array::from_iter(0..6, &[2, 3]);
1337/// let result = swap_axes(&a, 0, 1);
1338/// ```
1339pub fn swap_axes(a: impl AsRef<Array>, axis1: i32, axis2: i32) -> Result<Array> {
1340    let stream = Stream::thread_local_or_default();
1341    Array::try_from_op(|res| unsafe {
1342        mlx_sys::mlx_swapaxes(
1343            res,
1344            a.as_ref().as_ptr(),
1345            axis1,
1346            axis2,
1347            stream.as_ref().as_ptr(),
1348        )
1349    })
1350}
1351
1352/// Compatibility shim for [`swap_axes`].
1353#[generate_macro(customize(forwarding_shim = true))]
1354#[deprecated(
1355    since = "0.26.0",
1356    note = "use `with_stream` or `with_device` around `swap_axes`"
1357)]
1358pub fn swap_axes_device(
1359    a: impl AsRef<Array>,
1360    axis1: i32,
1361    axis2: i32,
1362    #[optional] stream: impl AsRef<Stream>,
1363) -> Result<Array> {
1364    crate::with_stream(stream.as_ref(), || swap_axes(a, axis1, axis2))
1365}
1366
1367/// Construct an array by repeating `a` the number of times given by `reps`.
1368///
1369/// # Params
1370///
1371/// - `a`: The input array.
1372/// - `reps`: The number of repetitions along each axis.
1373///
1374/// # Example
1375///
1376/// ```rust
1377/// use mlx_rs::{Array, ops::*};
1378///
1379/// let x = Array::from_slice(&[1, 2, 3], &[3]);
1380/// let y = tile(&x, &[2]);
1381/// ```
1382pub fn tile(a: impl AsRef<Array>, reps: &[i32]) -> Result<Array> {
1383    let stream = Stream::thread_local_or_default();
1384    Array::try_from_op(|res| unsafe {
1385        mlx_sys::mlx_tile(
1386            res,
1387            a.as_ref().as_ptr(),
1388            reps.as_ptr(),
1389            reps.len(),
1390            stream.as_ref().as_ptr(),
1391        )
1392    })
1393}
1394
1395/// Compatibility shim for [`tile`].
1396#[generate_macro(customize(forwarding_shim = true))]
1397#[deprecated(
1398    since = "0.26.0",
1399    note = "use `with_stream` or `with_device` around `tile`"
1400)]
1401pub fn tile_device(
1402    a: impl AsRef<Array>,
1403    reps: &[i32],
1404    #[optional] stream: impl AsRef<Stream>,
1405) -> Result<Array> {
1406    crate::with_stream(stream.as_ref(), || tile(a, reps))
1407}
1408
1409/// Transpose the dimensions of the array. Returns an error if the axes are invalid.
1410///
1411/// # Params
1412///
1413/// - `a`: The input array.
1414/// - `axes`: Specifies the source axis for each axis in the new array. The default is to reverse
1415///   the axes.
1416///
1417/// # Example
1418///
1419/// ```rust
1420/// use mlx_rs::{Array, ops::*};
1421///
1422/// let x = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
1423/// let y1 = transpose_axes(&x, &[0, 1]).unwrap();
1424/// let y2 = transpose(&x).unwrap();
1425/// ```
1426///
1427/// # See also
1428///
1429/// - [`transpose`]
1430pub fn transpose_axes(a: impl AsRef<Array>, axes: &[i32]) -> Result<Array> {
1431    let stream = Stream::thread_local_or_default();
1432    Array::try_from_op(|res| unsafe {
1433        mlx_sys::mlx_transpose_axes(
1434            res,
1435            a.as_ref().as_ptr(),
1436            axes.as_ptr(),
1437            axes.len(),
1438            stream.as_ref().as_ptr(),
1439        )
1440    })
1441}
1442
1443/// Compatibility shim for [`transpose_axes`].
1444#[generate_macro(customize(forwarding_shim = true))]
1445#[deprecated(
1446    since = "0.26.0",
1447    note = "use `with_stream` or `with_device` around `transpose_axes`"
1448)]
1449pub fn transpose_axes_device(
1450    a: impl AsRef<Array>,
1451    axes: &[i32],
1452    #[optional] stream: impl AsRef<Stream>,
1453) -> Result<Array> {
1454    crate::with_stream(stream.as_ref(), || transpose_axes(a, axes))
1455}
1456
1457/// Transpose with all axes reversed
1458pub fn transpose(a: impl AsRef<Array>) -> Result<Array> {
1459    let stream = Stream::thread_local_or_default();
1460    Array::try_from_op(|res| unsafe {
1461        mlx_sys::mlx_transpose(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1462    })
1463}
1464
1465/// Compatibility shim for [`transpose`].
1466#[generate_macro(customize(forwarding_shim = true))]
1467#[deprecated(
1468    since = "0.26.0",
1469    note = "use `with_stream` or `with_device` around `transpose`"
1470)]
1471pub fn transpose_device(
1472    a: impl AsRef<Array>,
1473    #[optional] stream: impl AsRef<Stream>,
1474) -> Result<Array> {
1475    crate::with_stream(stream.as_ref(), || transpose(a))
1476}
1477
1478// The unit tests below are adapted from
1479// https://github.com/ml-explore/mlx/blob/main/tests/ops_tests.cpp
1480#[cfg(test)]
1481mod tests {
1482    use crate::{
1483        array,
1484        test_utils::{assert_array_eq, tolerances},
1485        Array, Dtype,
1486    };
1487
1488    use super::*;
1489
1490    #[test]
1491    fn test_squeeze() {
1492        let a = Array::zeros::<i32>(&[2, 1, 2, 1, 2, 1]).unwrap();
1493        assert_eq!(
1494            squeeze_axes(&a, &[1, 3, 5][..]).unwrap().shape(),
1495            &[2, 2, 2]
1496        );
1497        assert_eq!(
1498            squeeze_axes(&a, &[-1, -3, -5][..]).unwrap().shape(),
1499            &[2, 2, 2]
1500        );
1501        assert_eq!(
1502            squeeze_axes(&a, &[1][..]).unwrap().shape(),
1503            &[2, 2, 1, 2, 1]
1504        );
1505        assert_eq!(
1506            squeeze_axes(&a, &[-1][..]).unwrap().shape(),
1507            &[2, 1, 2, 1, 2]
1508        );
1509
1510        assert!(squeeze_axes(&a, &[0][..]).is_err());
1511        assert!(squeeze_axes(&a, &[2][..]).is_err());
1512        assert!(squeeze_axes(&a, &[1, 3, 1][..]).is_err());
1513        assert!(squeeze_axes(&a, &[1, 3, -3][..]).is_err());
1514    }
1515
1516    #[test]
1517    fn test_expand_dims() {
1518        let a = Array::zeros::<i32>(&[2, 2]).unwrap();
1519        assert_eq!(expand_dims_axes(&a, &[0][..]).unwrap().shape(), &[1, 2, 2]);
1520        assert_eq!(expand_dims_axes(&a, &[-1][..]).unwrap().shape(), &[2, 2, 1]);
1521        assert_eq!(expand_dims_axes(&a, &[1][..]).unwrap().shape(), &[2, 1, 2]);
1522        assert_eq!(
1523            expand_dims_axes(&a, &[0, 1, 2]).unwrap().shape(),
1524            &[1, 1, 1, 2, 2]
1525        );
1526        assert_eq!(
1527            expand_dims_axes(&a, &[0, 1, 2, 5, 6, 7]).unwrap().shape(),
1528            &[1, 1, 1, 2, 2, 1, 1, 1]
1529        );
1530
1531        assert!(expand_dims_axes(&a, &[3]).is_err());
1532        assert!(expand_dims_axes(&a, &[0, 1, 0]).is_err());
1533        assert!(expand_dims_axes(&a, &[0, 1, -4]).is_err());
1534    }
1535
1536    #[test]
1537    fn test_flatten() {
1538        let x = Array::zeros::<i32>(&[2, 3, 4]).unwrap();
1539        assert_eq!(flatten(&x, None, None).unwrap().shape(), &[2 * 3 * 4]);
1540
1541        assert_eq!(flatten(&x, 1, 1).unwrap().shape(), &[2, 3, 4]);
1542        assert_eq!(flatten(&x, 1, 2).unwrap().shape(), &[2, 3 * 4]);
1543        assert_eq!(flatten(&x, 1, 3).unwrap().shape(), &[2, 3 * 4]);
1544        assert_eq!(flatten(&x, 1, -1).unwrap().shape(), &[2, 3 * 4]);
1545        assert_eq!(flatten(&x, -2, -1).unwrap().shape(), &[2, 3 * 4]);
1546        assert_eq!(flatten(&x, -3, -1).unwrap().shape(), &[2 * 3 * 4]);
1547        assert_eq!(flatten(&x, -4, -1).unwrap().shape(), &[2 * 3 * 4]);
1548
1549        assert!(flatten(&x, 2, 1).is_err());
1550
1551        assert!(flatten(&x, 5, 6).is_err());
1552
1553        assert!(flatten(&x, -5, -4).is_err());
1554
1555        let x = Array::from_int(1);
1556        assert_eq!(flatten(&x, -3, -1).unwrap().shape(), &[1]);
1557        assert_eq!(flatten(&x, 0, 0).unwrap().shape(), &[1]);
1558    }
1559
1560    #[test]
1561    fn test_unflatten() {
1562        let a = array!([1, 2, 3, 4]);
1563        let b = unflatten(&a, 0, &[2, -1]).unwrap();
1564        let expected = array!([[1, 2], [3, 4]]);
1565        assert_array_eq(b, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1566    }
1567
1568    #[test]
1569    fn test_reshape() {
1570        let x = Array::from_int(1);
1571        assert!(reshape(&x, &[]).unwrap().shape().is_empty());
1572        assert!(reshape(&x, &[2]).is_err());
1573        let y = reshape(&x, &[1, 1, 1]).unwrap();
1574        assert_eq!(y.shape(), &[1, 1, 1]);
1575        let y = reshape(&x, &[-1, 1, 1]).unwrap();
1576        assert_eq!(y.shape(), &[1, 1, 1]);
1577        let y = reshape(&x, &[1, 1, -1]).unwrap();
1578        assert_eq!(y.shape(), &[1, 1, 1]);
1579        assert!(reshape(&x, &[1, -1, -1]).is_err());
1580        assert!(reshape(&x, &[2, -1]).is_err());
1581
1582        let x = Array::zeros::<i32>(&[2, 2, 2]).unwrap();
1583        let y = reshape(&x, &[8]).unwrap();
1584        assert_eq!(y.shape(), &[8]);
1585        assert!(reshape(&x, &[7]).is_err());
1586        let y = reshape(&x, &[-1]).unwrap();
1587        assert_eq!(y.shape(), &[8]);
1588        let y = reshape(&x, &[-1, 2]).unwrap();
1589        assert_eq!(y.shape(), &[4, 2]);
1590        assert!(reshape(&x, &[-1, 7]).is_err());
1591
1592        let x = Array::from_slice::<i32>(&[], &[0]);
1593        let y = reshape(&x, &[0, 0, 0]).unwrap();
1594        assert_eq!(y.shape(), &[0, 0, 0]);
1595        y.eval().unwrap();
1596        assert_eq!(y.size(), 0);
1597        assert!(reshape(&x, &[]).is_err());
1598        assert!(reshape(&x, &[1]).is_err());
1599        let y = reshape(&x, &[1, 5, 0]).unwrap();
1600        assert_eq!(y.shape(), &[1, 5, 0]);
1601    }
1602
1603    #[test]
1604    fn test_as_strided() {
1605        let x = Array::from_iter(0..10, &[10]);
1606        let y = as_strided(&x, &[3, 3][..], &[1, 1][..], 0).unwrap();
1607        let expected = Array::from_slice(&[0, 1, 2, 1, 2, 3, 2, 3, 4], &[3, 3]);
1608        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1609
1610        let y = as_strided(&x, &[3, 3][..], &[0, 3][..], 0).unwrap();
1611        let expected = Array::from_slice(&[0, 3, 6, 0, 3, 6, 0, 3, 6], &[3, 3]);
1612        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1613
1614        let x = x.reshape(&[2, 5]).unwrap();
1615        let x = x.transpose_axes(&[1, 0][..]).unwrap();
1616        let y = as_strided(&x, &[3, 3][..], &[2, 1][..], 1).unwrap();
1617        let expected = Array::from_slice(&[5, 1, 6, 6, 2, 7, 7, 3, 8], &[3, 3]);
1618        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1619    }
1620
1621    #[test]
1622    fn test_at_least_1d() {
1623        let x = Array::from_int(1);
1624        let out = at_least_1d(&x).unwrap();
1625        assert_eq!(out.ndim(), 1);
1626        assert_eq!(out.shape(), &[1]);
1627
1628        let x = Array::from_slice(&[1, 2, 3], &[3]);
1629        let out = at_least_1d(&x).unwrap();
1630        assert_eq!(out.ndim(), 1);
1631        assert_eq!(out.shape(), &[3]);
1632
1633        let x = Array::from_slice(&[1, 2, 3], &[3, 1]);
1634        let out = at_least_1d(&x).unwrap();
1635        assert_eq!(out.ndim(), 2);
1636        assert_eq!(out.shape(), &[3, 1]);
1637    }
1638
1639    #[test]
1640    fn test_at_least_2d() {
1641        let x = Array::from_int(1);
1642        let out = at_least_2d(&x).unwrap();
1643        assert_eq!(out.ndim(), 2);
1644        assert_eq!(out.shape(), &[1, 1]);
1645
1646        let x = Array::from_slice(&[1, 2, 3], &[3]);
1647        let out = at_least_2d(&x).unwrap();
1648        assert_eq!(out.ndim(), 2);
1649        assert_eq!(out.shape(), &[1, 3]);
1650
1651        let x = Array::from_slice(&[1, 2, 3], &[3, 1]);
1652        let out = at_least_2d(&x).unwrap();
1653        assert_eq!(out.ndim(), 2);
1654        assert_eq!(out.shape(), &[3, 1]);
1655    }
1656
1657    #[test]
1658    fn test_at_least_3d() {
1659        let x = Array::from_int(1);
1660        let out = at_least_3d(&x).unwrap();
1661        assert_eq!(out.ndim(), 3);
1662        assert_eq!(out.shape(), &[1, 1, 1]);
1663
1664        let x = Array::from_slice(&[1, 2, 3], &[3]);
1665        let out = at_least_3d(&x).unwrap();
1666        assert_eq!(out.ndim(), 3);
1667        assert_eq!(out.shape(), &[1, 3, 1]);
1668
1669        let x = Array::from_slice(&[1, 2, 3], &[3, 1]);
1670        let out = at_least_3d(&x).unwrap();
1671        assert_eq!(out.ndim(), 3);
1672        assert_eq!(out.shape(), &[3, 1, 1]);
1673    }
1674
1675    #[test]
1676    fn test_move_axis() {
1677        let a = Array::from_int(0);
1678        assert!(move_axis(&a, 0, 0).is_err());
1679
1680        let a = Array::zeros::<i32>(&[2]).unwrap();
1681        assert!(move_axis(&a, 0, 1).is_err());
1682        assert_eq!(move_axis(&a, 0, 0).unwrap().shape(), &[2]);
1683        assert_eq!(move_axis(&a, -1, -1).unwrap().shape(), &[2]);
1684
1685        let a = Array::zeros::<i32>(&[2, 3, 4]).unwrap();
1686        assert!(move_axis(&a, 0, -4).is_err());
1687        assert!(move_axis(&a, 0, 3).is_err());
1688        assert!(move_axis(&a, 3, 0).is_err());
1689        assert!(move_axis(&a, -4, 0).is_err());
1690        assert_eq!(move_axis(&a, 0, 2).unwrap().shape(), &[3, 4, 2]);
1691        assert_eq!(move_axis(&a, 0, 1).unwrap().shape(), &[3, 2, 4]);
1692        assert_eq!(move_axis(&a, 0, -1).unwrap().shape(), &[3, 4, 2]);
1693        assert_eq!(move_axis(&a, -2, 2).unwrap().shape(), &[2, 4, 3]);
1694    }
1695
1696    #[test]
1697    fn test_concatenate() {
1698        let a = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
1699        let b = Array::from_slice(&[5, 6], &[1, 2]);
1700        assert_eq!(concatenate(&[&a, &b], 0).unwrap().shape(), &[3, 2]);
1701
1702        let flat = concatenate_flat(&[&a, &b]).unwrap();
1703        assert_eq!(flat.shape(), &[6]);
1704        assert_eq!(flat.as_slice::<i32>(), &[1, 2, 3, 4, 5, 6]);
1705    }
1706
1707    #[test]
1708    fn test_split_equal() {
1709        let x = Array::from_int(3);
1710        assert!(split_equal(&x, 0, 0).is_err());
1711
1712        let x = Array::from_slice(&[0, 1, 2], &[3]);
1713        assert!(split_equal(&x, 3, 1).is_err());
1714        assert!(split_equal(&x, -2, 1).is_err());
1715
1716        let out = split_equal(&x, 3, 0).unwrap();
1717        assert_eq!(out.len(), 3);
1718
1719        let mut out = split_equal(&x, 3, -1).unwrap();
1720        assert_eq!(out.len(), 3);
1721        for (i, a) in out.iter_mut().enumerate() {
1722            assert_eq!(a.shape(), &[1]);
1723            assert_eq!(a.dtype(), Dtype::Int32);
1724            assert_eq!(a.item_exact::<i32>(), i as i32);
1725        }
1726
1727        let x = Array::from_slice(&[0, 1, 2, 3, 4, 5], &[2, 3]);
1728        let out = split_equal(&x, 2, None).unwrap();
1729        assert_array_eq(
1730            &out[0],
1731            Array::from_slice(&[0, 1, 2], &[1, 3]),
1732            tolerances::EXACT.rtol,
1733            tolerances::EXACT.atol,
1734        );
1735        assert_array_eq(
1736            &out[1],
1737            Array::from_slice(&[3, 4, 5], &[1, 3]),
1738            tolerances::EXACT.rtol,
1739            tolerances::EXACT.atol,
1740        );
1741
1742        let out = split_equal(&x, 3, 1).unwrap();
1743        assert_array_eq(
1744            &out[0],
1745            Array::from_slice(&[0, 3], &[2, 1]),
1746            tolerances::EXACT.rtol,
1747            tolerances::EXACT.atol,
1748        );
1749        assert_array_eq(
1750            &out[1],
1751            Array::from_slice(&[1, 4], &[2, 1]),
1752            tolerances::EXACT.rtol,
1753            tolerances::EXACT.atol,
1754        );
1755        assert_array_eq(
1756            &out[2],
1757            Array::from_slice(&[2, 5], &[2, 1]),
1758            tolerances::EXACT.rtol,
1759            tolerances::EXACT.atol,
1760        );
1761
1762        let x = Array::zeros::<i32>(&[8, 12]).unwrap();
1763        let out = split_equal(&x, 2, None).unwrap();
1764        assert_eq!(out.len(), 2);
1765        assert_eq!(out[0].shape(), &[4, 12]);
1766        assert_eq!(out[1].shape(), &[4, 12]);
1767
1768        let out = split_equal(&x, 3, 1).unwrap();
1769        assert_eq!(out.len(), 3);
1770        assert_eq!(out[0].shape(), &[8, 4]);
1771        assert_eq!(out[1].shape(), &[8, 4]);
1772        assert_eq!(out[2].shape(), &[8, 4]);
1773    }
1774
1775    #[test]
1776    fn test_split_at_indices() {
1777        let x = Array::zeros::<i32>(&[8, 12]).unwrap();
1778
1779        let out = split_at_indices(&x, &[], None).unwrap();
1780        assert_eq!(out.len(), 1);
1781        assert_eq!(out[0].shape(), x.shape());
1782
1783        let out = split_at_indices(&x, &[3, 7], None).unwrap();
1784        assert_eq!(out.len(), 3);
1785        assert_eq!(out[0].shape(), &[3, 12]);
1786        assert_eq!(out[1].shape(), &[4, 12]);
1787        assert_eq!(out[2].shape(), &[1, 12]);
1788
1789        let out = split_at_indices(&x, &[20], None).unwrap();
1790        assert_eq!(out.len(), 2);
1791        assert_eq!(out[0].shape(), &[8, 12]);
1792        assert_eq!(out[1].shape(), &[0, 12]);
1793
1794        let out = split_at_indices(&x, &[-5], None).unwrap();
1795        assert_eq!(out[0].shape(), &[3, 12]);
1796        assert_eq!(out[1].shape(), &[5, 12]);
1797
1798        let out = split_at_indices(&x, &[2, 8], Some(1)).unwrap();
1799        assert_eq!(out[0].shape(), &[8, 2]);
1800        assert_eq!(out[1].shape(), &[8, 6]);
1801        assert_eq!(out[2].shape(), &[8, 4]);
1802
1803        let x = Array::from_iter(0i32..5, &[5]);
1804        let out = split_at_indices(&x, &[2, 1, 2], None).unwrap();
1805        assert_array_eq(
1806            &out[0],
1807            Array::from_slice(&[0, 1], &[2]),
1808            tolerances::EXACT.rtol,
1809            tolerances::EXACT.atol,
1810        );
1811        assert_array_eq(
1812            &out[1],
1813            Array::from_slice::<i32>(&[], &[0]),
1814            tolerances::EXACT.rtol,
1815            tolerances::EXACT.atol,
1816        );
1817        assert_array_eq(
1818            &out[2],
1819            Array::from_slice(&[1], &[1]),
1820            tolerances::EXACT.rtol,
1821            tolerances::EXACT.atol,
1822        );
1823        assert_array_eq(
1824            &out[3],
1825            Array::from_slice(&[2, 3, 4], &[3]),
1826            tolerances::EXACT.rtol,
1827            tolerances::EXACT.atol,
1828        );
1829    }
1830
1831    #[test]
1832    fn test_pad() {
1833        let x = Array::zeros::<f32>(&[1, 2, 3]).unwrap();
1834        assert_eq!(pad(&x, 1, None, None).unwrap().shape(), &[3, 4, 5]);
1835        assert_eq!(pad(&x, (0, 1), None, None).unwrap().shape(), &[2, 3, 4]);
1836        assert_eq!(
1837            pad(&x, &[(1, 1), (1, 2), (3, 1)], None, None)
1838                .unwrap()
1839                .shape(),
1840            &[3, 5, 7]
1841        );
1842    }
1843
1844    #[test]
1845    fn test_stack() {
1846        let x = Array::from_slice::<f32>(&[], &[0]);
1847        let x = vec![x];
1848        assert_eq!(stack(&x, 0).unwrap().shape(), &[1, 0]);
1849        assert_eq!(stack(&x, 1).unwrap().shape(), &[0, 1]);
1850
1851        let x = Array::from_slice(&[1, 2, 3], &[3]);
1852        let x = vec![x];
1853        assert_eq!(stack(&x, 0).unwrap().shape(), &[1, 3]);
1854        assert_eq!(stack(&x, 1).unwrap().shape(), &[3, 1]);
1855
1856        let y = Array::from_slice(&[4, 5, 6], &[3]);
1857        let mut z = x;
1858        z.push(y);
1859        assert_eq!(stack(&z, 0).unwrap().shape(), &[2, 3]);
1860        assert_eq!(stack(&z, 1).unwrap().shape(), &[3, 2]);
1861        assert_eq!(stack(&z, -1).unwrap().shape(), &[3, 2]);
1862        assert_eq!(stack(&z, -2).unwrap().shape(), &[2, 3]);
1863
1864        let empty: Vec<Array> = Vec::new();
1865        assert!(stack(&empty, 0).is_err());
1866
1867        let x = Array::from_slice(&[1, 2, 3], &[3])
1868            .as_dtype(Dtype::Float16)
1869            .unwrap();
1870        let y = Array::from_slice(&[4, 5, 6], &[3])
1871            .as_dtype(Dtype::Int32)
1872            .unwrap();
1873        assert_eq!(stack(&[x, y], 0).unwrap().dtype(), Dtype::Float16);
1874
1875        let x = Array::from_slice(&[1, 2, 3], &[3])
1876            .as_dtype(Dtype::Int32)
1877            .unwrap();
1878        let y = Array::from_slice(&[4, 5, 6, 7], &[4])
1879            .as_dtype(Dtype::Int32)
1880            .unwrap();
1881        assert!(stack(&[x, y], 0).is_err());
1882    }
1883
1884    #[test]
1885    fn contiguous_materializes_row_major_for_slice_observers() {
1886        let input = Array::from_slice(&[1_i32, 2, 3, 4, 5, 6], &[2, 3]);
1887        let transposed = input.transpose().unwrap();
1888        assert!(matches!(
1889            transposed.try_as_slice::<i32>(),
1890            Err(crate::error::AsSliceError::NotContiguous)
1891        ));
1892
1893        let contiguous = transposed.contiguous().unwrap();
1894        assert_eq!(
1895            contiguous.try_as_slice::<i32>().unwrap(),
1896            &[1, 4, 2, 5, 3, 6]
1897        );
1898        assert!(bool::try_from_op(|res| unsafe {
1899            mlx_sys::_mlx_array_is_row_contiguous(res, contiguous.as_ptr())
1900        })
1901        .unwrap());
1902    }
1903
1904    #[test]
1905    fn contiguous_options_can_retain_column_major_layout() {
1906        let input = Array::from_slice(&[1_i32, 2, 3, 4, 5, 6], &[2, 3]);
1907        let transposed = input.transpose().unwrap();
1908        let contiguous = transposed
1909            .contiguous_with_options(ContiguousOptions {
1910                allow_col_major: true,
1911            })
1912            .unwrap();
1913        contiguous.eval().unwrap();
1914
1915        assert!(!bool::try_from_op(|res| unsafe {
1916            mlx_sys::_mlx_array_is_row_contiguous(res, contiguous.as_ptr())
1917        })
1918        .unwrap());
1919        assert!(bool::try_from_op(|res| unsafe {
1920            mlx_sys::_mlx_array_is_col_contiguous(res, contiguous.as_ptr())
1921        })
1922        .unwrap());
1923        assert!(matches!(
1924            contiguous.try_as_slice::<i32>(),
1925            Err(crate::error::AsSliceError::NotContiguous)
1926        ));
1927    }
1928
1929    #[test]
1930    fn test_swap_axes() {
1931        let a = Array::from_int(0);
1932        assert!(swap_axes(&a, 0, 0).is_err());
1933
1934        let a = Array::zeros::<i32>(&[2]).unwrap();
1935        assert!(swap_axes(&a, 0, 1).is_err());
1936        assert_eq!(swap_axes(&a, 0, 0).unwrap().shape(), &[2]);
1937        assert_eq!(swap_axes(&a, -1, -1).unwrap().shape(), &[2]);
1938
1939        let a = Array::zeros::<i32>(&[2, 3, 4]).unwrap();
1940        assert!(swap_axes(&a, 0, -4).is_err());
1941        assert!(swap_axes(&a, 0, 3).is_err());
1942        assert!(swap_axes(&a, 3, 0).is_err());
1943        assert!(swap_axes(&a, -4, 0).is_err());
1944        assert_eq!(swap_axes(&a, 0, 2).unwrap().shape(), &[4, 3, 2]);
1945        assert_eq!(swap_axes(&a, 0, 1).unwrap().shape(), &[3, 2, 4]);
1946        assert_eq!(swap_axes(&a, 0, -1).unwrap().shape(), &[4, 3, 2]);
1947        assert_eq!(swap_axes(&a, -2, 2).unwrap().shape(), &[2, 4, 3]);
1948    }
1949
1950    #[test]
1951    fn test_tile() {
1952        let x = Array::from_slice(&[1, 2, 3], &[3]);
1953        let y = tile(&x, &[2]).unwrap();
1954        let expected = Array::from_slice(&[1, 2, 3, 1, 2, 3], &[6]);
1955        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1956
1957        let x = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
1958        let y = tile(&x, &[2]).unwrap();
1959        let expected = Array::from_slice(&[1, 2, 1, 2, 3, 4, 3, 4], &[2, 4]);
1960        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1961
1962        let x = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
1963        let y = tile(&x, &[4, 1]).unwrap();
1964        let expected =
1965            Array::from_slice(&[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], &[8, 2]);
1966        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1967
1968        let x = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
1969        let y = tile(&x, &[2, 2]).unwrap();
1970        let expected =
1971            Array::from_slice(&[1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 3, 4, 3, 4], &[4, 4]);
1972        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1973
1974        let x = Array::from_slice(&[1, 2, 3], &[3]);
1975        let y = tile(&x, &[2, 2, 2]).unwrap();
1976        let expected = Array::from_slice(
1977            &[
1978                1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3,
1979            ],
1980            &[2, 2, 6],
1981        );
1982        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
1983    }
1984
1985    #[test]
1986    fn test_transpose() {
1987        let x = Array::from_int(1);
1988        let y = transpose(&x).unwrap();
1989        assert!(y.shape().is_empty());
1990        assert_eq!(y.item_exact::<i32>(), 1);
1991        assert!(transpose_axes(&x, &[0][..]).is_err());
1992        assert!(transpose_axes(&x, &[1][..]).is_err());
1993
1994        let x = Array::from_slice(&[1], &[1]);
1995        let y = transpose(&x).unwrap();
1996        assert_eq!(y.shape(), &[1]);
1997        assert_eq!(y.item_exact::<i32>(), 1);
1998
1999        let y = transpose_axes(&x, &[-1][..]).unwrap();
2000        assert_eq!(y.shape(), &[1]);
2001        assert_eq!(y.item_exact::<i32>(), 1);
2002
2003        assert!(transpose_axes(&x, &[1][..]).is_err());
2004        assert!(transpose_axes(&x, &[0, 0][..]).is_err());
2005
2006        let x = Array::from_slice::<i32>(&[], &[0]);
2007        let y = transpose(&x).unwrap();
2008        assert_eq!(y.shape(), &[0]);
2009        y.eval().unwrap();
2010        assert_eq!(y.size(), 0);
2011
2012        let x = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
2013        let mut y = transpose(&x).unwrap();
2014        assert_eq!(y.shape(), &[3, 2]);
2015        y = transpose_axes(&x, &[-1, 0][..]).unwrap();
2016        assert_eq!(y.shape(), &[3, 2]);
2017        y = transpose_axes(&x, &[-1, -2][..]).unwrap();
2018        assert_eq!(y.shape(), &[3, 2]);
2019        y.eval().unwrap();
2020        assert_array_eq(
2021            y,
2022            Array::from_slice(&[1, 4, 2, 5, 3, 6], &[3, 2]),
2023            tolerances::EXACT.rtol,
2024            tolerances::EXACT.atol,
2025        );
2026
2027        let y = transpose_axes(&x, &[0, 1][..]).unwrap();
2028        assert_eq!(y.shape(), &[2, 3]);
2029        assert_array_eq(y, &x, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2030
2031        let y = transpose_axes(&x, &[0, -1][..]).unwrap();
2032        assert_eq!(y.shape(), &[2, 3]);
2033        assert_array_eq(y, &x, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2034
2035        assert!(transpose_axes(&x, &[][..]).is_err());
2036        assert!(transpose_axes(&x, &[0][..]).is_err());
2037        assert!(transpose_axes(&x, &[0, 0][..]).is_err());
2038        assert!(transpose_axes(&x, &[0, 0, 0][..]).is_err());
2039        assert!(transpose_axes(&x, &[0, 1, 1][..]).is_err());
2040
2041        let x = Array::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], &[2, 3, 2]);
2042        let y = transpose(&x).unwrap();
2043        assert_eq!(y.shape(), &[2, 3, 2]);
2044        let expected = Array::from_slice(&[1, 7, 3, 9, 5, 11, 2, 8, 4, 10, 6, 12], &[2, 3, 2]);
2045        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2046
2047        let y = transpose_axes(&x, &[0, 1, 2][..]).unwrap();
2048        assert_eq!(y.shape(), &[2, 3, 2]);
2049        assert_array_eq(y, &x, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2050
2051        let y = transpose_axes(&x, &[1, 0, 2][..]).unwrap();
2052        assert_eq!(y.shape(), &[3, 2, 2]);
2053        let expected = Array::from_slice(&[1, 2, 7, 8, 3, 4, 9, 10, 5, 6, 11, 12], &[3, 2, 2]);
2054        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2055
2056        let y = transpose_axes(&x, &[0, 2, 1][..]).unwrap();
2057        assert_eq!(y.shape(), &[2, 2, 3]);
2058        let expected = Array::from_slice(&[1, 3, 5, 2, 4, 6, 7, 9, 11, 8, 10, 12], &[2, 2, 3]);
2059        assert_array_eq(y, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2060
2061        let mut x = Array::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7], &[4, 2]);
2062        x = reshape(transpose(&x).unwrap(), &[2, 2, 2]).unwrap();
2063        let expected = Array::from_slice(&[0, 2, 4, 6, 1, 3, 5, 7], &[2, 2, 2]);
2064        assert_array_eq(x, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
2065
2066        let mut x = Array::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7], &[1, 4, 1, 2]);
2067        // assert!(x.flags().row_contiguous);
2068        x = transpose_axes(&x, &[2, 1, 0, 3][..]).unwrap();
2069        x.eval().unwrap();
2070        // assert!(x.flags().row_contiguous);
2071    }
2072}