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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
15pub struct ContiguousOptions {
16 pub allow_col_major: bool,
18}
19
20impl Array {
21 pub fn contiguous(&self) -> Result<Array> {
25 self.contiguous_with_options(ContiguousOptions::default())
26 }
27
28 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 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 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 pub fn expand_dims(&self, axis: i32) -> Result<Array> {
106 expand_dims(self, axis)
107 }
108
109 #[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 pub fn expand_dims_axes(&self, axes: &[i32]) -> Result<Array> {
120 expand_dims_axes(self, axes)
121 }
122
123 #[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 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 #[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 pub fn reshape(&self, shape: &[i32]) -> Result<Array> {
161 reshape(self, shape)
162 }
163
164 #[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 pub fn squeeze_axes(&self, axes: &[i32]) -> Result<Array> {
175 squeeze_axes(self, axes)
176 }
177
178 #[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 pub fn squeeze(&self) -> Result<Array> {
189 squeeze(self)
190 }
191
192 #[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 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 #[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 pub fn at_least_1d(&self) -> Result<Array> {
228 at_least_1d(self)
229 }
230
231 #[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 pub fn at_least_2d(&self) -> Result<Array> {
242 at_least_2d(self)
243 }
244
245 #[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 pub fn at_least_3d(&self) -> Result<Array> {
256 at_least_3d(self)
257 }
258
259 #[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 pub fn move_axis(&self, src: i32, dst: i32) -> Result<Array> {
270 move_axis(self, src, dst)
271 }
272
273 #[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 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 #[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 #[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 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 #[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 #[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 pub fn swap_axes(&self, axis1: i32, axis2: i32) -> Result<Array> {
343 swap_axes(self, axis1, axis2)
344 }
345
346 #[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 pub fn transpose_axes(&self, axes: &[i32]) -> Result<Array> {
362 transpose_axes(self, axes)
363 }
364
365 #[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 pub fn transpose(&self) -> Result<Array> {
376 transpose(self)
377 }
378
379 #[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 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
414pub 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#[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
441pub 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#[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
493pub 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#[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
535pub 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#[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#[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
579pub 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#[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
601pub 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#[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
643pub 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#[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
665pub 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#[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
720pub 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#[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
756pub 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#[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
798pub 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#[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
841pub 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#[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
863pub 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#[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
897pub 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#[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
931pub 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#[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
965pub 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#[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
1003pub 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#[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#[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
1063pub 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#[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#[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#[derive(Debug)]
1125pub enum PadWidth<'a> {
1126 Same((i32, i32)),
1128
1129 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#[derive(Debug)]
1174pub enum PadMode {
1175 Constant,
1177
1178 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
1194pub 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#[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
1266pub 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#[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#[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#[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
1323pub 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#[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
1367pub 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#[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
1409pub 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#[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
1457pub 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#[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#[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 x = transpose_axes(&x, &[2, 1, 0, 3][..]).unwrap();
2069 x.eval().unwrap();
2070 }
2072}