Skip to main content

mlx_rs/ops/
factory.rs

1use crate::array::Array;
2use crate::array::ArrayElement;
3use crate::error::Result;
4use crate::utils::guard::Guarded;
5use crate::{Dtype, Stream};
6use mlx_internal_macros::generate_macro;
7use num_traits::NumCast;
8
9/// Sample count and endpoint behavior for [`linspace`].
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct LinspaceOptions {
12    /// Number of samples.
13    pub count: i32,
14
15    /// Include `stop` as the final sample.
16    pub endpoint: bool,
17}
18
19impl Default for LinspaceOptions {
20    fn default() -> Self {
21        Self {
22            count: 50,
23            endpoint: true,
24        }
25    }
26}
27
28impl Array {
29    /// Construct an array of zeros returning an error if shape is invalid.
30    ///
31    /// # Params
32    ///
33    /// - shape: Desired shape
34    ///
35    /// # Example
36    ///
37    /// ```rust
38    /// use mlx_rs::Array;
39    /// Array::zeros::<f32>(&[5, 10]).unwrap();
40    /// ```
41    pub fn zeros<T: ArrayElement>(shape: &[i32]) -> Result<Array> {
42        let dtype = T::DTYPE;
43        zeros_dtype(shape, dtype)
44    }
45
46    /// Compatibility shim for [`zeros`].
47    #[deprecated(
48        since = "0.26.0",
49        note = "use `with_stream` or `with_device` around `zeros`"
50    )]
51    pub fn zeros_device<T: ArrayElement>(
52        shape: &[i32],
53        stream: impl AsRef<Stream>,
54    ) -> Result<Array> {
55        crate::with_stream(stream.as_ref(), || Self::zeros::<T>(shape))
56    }
57
58    /// Construct an array of ones returning an error if shape is invalid.
59    ///
60    /// # Params
61    ///
62    /// - shape: Desired shape
63    ///
64    /// # Example
65    ///
66    /// ```rust
67    /// use mlx_rs::Array;
68    /// Array::ones::<f32>(&[5, 10]).unwrap();
69    /// ```
70    pub fn ones<T: ArrayElement>(shape: &[i32]) -> Result<Array> {
71        let dtype = T::DTYPE;
72        ones_dtype(shape, dtype)
73    }
74
75    /// Compatibility shim for [`ones`].
76    #[deprecated(
77        since = "0.26.0",
78        note = "use `with_stream` or `with_device` around `ones`"
79    )]
80    pub fn ones_device<T: ArrayElement>(
81        shape: &[i32],
82        stream: impl AsRef<Stream>,
83    ) -> Result<Array> {
84        crate::with_stream(stream.as_ref(), || Self::ones::<T>(shape))
85    }
86
87    /// Create an identity matrix or a general diagonal matrix returning an error if params are invalid.
88    ///
89    /// # Params
90    ///
91    /// - n: number of rows in the output
92    /// - m: number of columns in the output -- equal to `n` if not specified
93    /// - k: index of the diagonal - defaults to 0 if not specified
94    ///
95    /// # Example
96    ///
97    /// ```rust
98    /// use mlx_rs::Array;
99    /// //  create [10, 10] array with 1's on the diagonal.
100    /// let r = Array::eye::<f32>(10, None, None).unwrap();
101    /// ```
102    pub fn eye<T: ArrayElement>(n: i32, m: Option<i32>, k: Option<i32>) -> Result<Array> {
103        let stream = Stream::thread_local_or_default();
104        Array::try_from_op(|res| unsafe {
105            mlx_sys::mlx_eye(
106                res,
107                n,
108                m.unwrap_or(n),
109                k.unwrap_or(0),
110                T::DTYPE.into(),
111                stream.as_ref().as_ptr(),
112            )
113        })
114    }
115
116    /// Compatibility shim for [`eye`].
117    #[deprecated(
118        since = "0.26.0",
119        note = "use `with_stream` or `with_device` around `eye`"
120    )]
121    pub fn eye_device<T: ArrayElement>(
122        n: i32,
123        m: Option<i32>,
124        k: Option<i32>,
125        stream: impl AsRef<Stream>,
126    ) -> Result<Array> {
127        crate::with_stream(stream.as_ref(), || Self::eye::<T>(n, m, k))
128    }
129
130    /// Construct an array with the given value returning an error if shape is invalid.
131    ///
132    /// Constructs an array of size `shape` filled with `values`. If `values`
133    /// is an [Array] it must be [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting) to the given `shape`.
134    ///
135    /// # Params
136    ///
137    /// - shape: shape of the output array
138    /// - values: values to be broadcast into the array
139    ///
140    /// # Example
141    ///
142    /// ```rust
143    /// use mlx_rs::{Array, array};
144    /// //  create [5, 4] array filled with 7
145    /// let r = Array::full::<f32>(&[5, 4], array!(7.0f32)).unwrap();
146    /// ```
147    pub fn full<T: ArrayElement>(shape: &[i32], values: impl AsRef<Array>) -> Result<Array> {
148        let stream = Stream::thread_local_or_default();
149        Array::try_from_op(|res| unsafe {
150            mlx_sys::mlx_full(
151                res,
152                shape.as_ptr(),
153                shape.len(),
154                values.as_ref().as_ptr(),
155                T::DTYPE.into(),
156                stream.as_ref().as_ptr(),
157            )
158        })
159    }
160
161    /// Compatibility shim for [`full`].
162    #[deprecated(
163        since = "0.26.0",
164        note = "use `with_stream` or `with_device` around `full`"
165    )]
166    pub fn full_device<T: ArrayElement>(
167        shape: &[i32],
168        values: impl AsRef<Array>,
169        stream: impl AsRef<Stream>,
170    ) -> Result<Array> {
171        crate::with_stream(stream.as_ref(), || Self::full::<T>(shape, values))
172    }
173
174    /// Create a square identity matrix returning an error if params are invalid.
175    ///
176    /// # Params
177    ///
178    /// - n: number of rows and columns in the output
179    ///
180    /// # Example
181    ///
182    /// ```rust
183    /// use mlx_rs::Array;
184    /// //  create [10, 10] array with 1's on the diagonal.
185    /// let r = Array::identity::<f32>(10).unwrap();
186    /// ```
187    pub fn identity<T: ArrayElement>(n: i32) -> Result<Array> {
188        let stream = Stream::thread_local_or_default();
189        Array::try_from_op(|res| unsafe {
190            mlx_sys::mlx_identity(res, n, T::DTYPE.into(), stream.as_ref().as_ptr())
191        })
192    }
193
194    /// Compatibility shim for [`identity`].
195    #[deprecated(
196        since = "0.26.0",
197        note = "use `with_stream` or `with_device` around `identity`"
198    )]
199    pub fn identity_device<T: ArrayElement>(n: i32, stream: impl AsRef<Stream>) -> Result<Array> {
200        crate::with_stream(stream.as_ref(), || Self::identity::<T>(n))
201    }
202
203    /// Generates ranges of numbers.
204    ///
205    /// Generate numbers in the half-open interval `[start, stop)` in increments of `step`.
206    ///
207    /// # Params
208    ///
209    /// - `start`: Starting value which defaults to `0`.
210    /// - `stop`: Stopping value.
211    /// - `step`: Increment which defaults to `1`.
212    ///
213    /// # Example
214    ///
215    /// ```rust
216    /// use mlx_rs::Array;
217    ///
218    /// // Create a 1-D array with values from 0 to 50
219    /// let r = Array::arange::<_, f32>(None, 50, None);
220    /// ```
221    pub fn arange<U, T>(
222        start: impl Into<Option<U>>,
223        stop: U,
224        step: impl Into<Option<U>>,
225    ) -> Result<Array>
226    where
227        U: NumCast,
228        T: ArrayElement,
229    {
230        let stream = Stream::thread_local_or_default();
231        let start: f64 = start.into().and_then(NumCast::from).unwrap_or(0.0);
232        let stop: f64 = NumCast::from(stop).unwrap();
233        let step: f64 = step.into().and_then(NumCast::from).unwrap_or(1.0);
234
235        Array::try_from_op(|res| unsafe {
236            mlx_sys::mlx_arange(
237                res,
238                start,
239                stop,
240                step,
241                T::DTYPE.into(),
242                stream.as_ref().as_ptr(),
243            )
244        })
245    }
246
247    /// Compatibility shim for [`arange`].
248    #[deprecated(
249        since = "0.26.0",
250        note = "use `with_stream` or `with_device` around `arange`"
251    )]
252    pub fn arange_device<U, T>(
253        start: impl Into<Option<U>>,
254        stop: U,
255        step: impl Into<Option<U>>,
256        stream: impl AsRef<Stream>,
257    ) -> Result<Array>
258    where
259        U: NumCast,
260        T: ArrayElement,
261    {
262        crate::with_stream(stream.as_ref(), || Self::arange::<U, T>(start, stop, step))
263    }
264
265    /// Compatibility shim for [`linspace`] with endpoint inclusion.
266    #[deprecated(since = "0.26.0", note = "use `ops::linspace` with `LinspaceOptions`")]
267    pub fn linspace<U, T>(start: U, stop: U, count: impl Into<Option<i32>>) -> Result<Array>
268    where
269        U: NumCast,
270        T: ArrayElement,
271    {
272        linspace::<U, T>(
273            start,
274            stop,
275            LinspaceOptions {
276                count: count.into().unwrap_or(50),
277                endpoint: true,
278            },
279        )
280    }
281
282    /// Compatibility shim for [`linspace`].
283    #[deprecated(
284        since = "0.26.0",
285        note = "use `with_stream` or `with_device` around `ops::linspace`"
286    )]
287    pub fn linspace_device<U, T>(
288        start: U,
289        stop: U,
290        count: impl Into<Option<i32>>,
291        stream: impl AsRef<Stream>,
292    ) -> Result<Array>
293    where
294        U: NumCast,
295        T: ArrayElement,
296    {
297        crate::with_stream(stream.as_ref(), || {
298            linspace::<U, T>(
299                start,
300                stop,
301                LinspaceOptions {
302                    count: count.into().unwrap_or(50),
303                    endpoint: true,
304                },
305            )
306        })
307    }
308
309    /// Repeat an array along a specified axis returning an error if params are invalid.
310    ///
311    /// # Params
312    ///
313    /// - array: array to repeat
314    /// - count: number of times to repeat
315    /// - axis: axis to repeat along
316    ///
317    /// # Example
318    ///
319    /// ```rust
320    /// use mlx_rs::Array;
321    /// // repeat a [2, 2] array 4 times along axis 1
322    /// let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
323    /// let r = Array::repeat_axis::<i32>(source, 4, 1).unwrap();
324    /// ```
325    pub fn repeat_axis<T: ArrayElement>(array: Array, count: i32, axis: i32) -> Result<Array> {
326        let stream = Stream::thread_local_or_default();
327        Array::try_from_op(|res| unsafe {
328            mlx_sys::mlx_repeat_axis(res, array.as_ptr(), count, axis, stream.as_ref().as_ptr())
329        })
330    }
331
332    /// Compatibility shim for [`repeat_axis`].
333    #[deprecated(
334        since = "0.26.0",
335        note = "use `with_stream` or `with_device` around `repeat_axis`"
336    )]
337    pub fn repeat_axis_device<T: ArrayElement>(
338        array: Array,
339        count: i32,
340        axis: i32,
341        stream: impl AsRef<Stream>,
342    ) -> Result<Array> {
343        crate::with_stream(stream.as_ref(), || {
344            Self::repeat_axis::<T>(array, count, axis)
345        })
346    }
347
348    /// Repeat a flattened array along axis 0 returning an error if params are invalid.
349    ///
350    /// # Params
351    ///
352    /// - array: array to repeat
353    /// - count: number of times to repeat
354    ///
355    /// # Example
356    ///
357    /// ```rust
358    /// use mlx_rs::Array;
359    /// // repeat a 4 element array 4 times along axis 0
360    /// let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
361    /// let r = Array::repeat::<i32>(source, 4).unwrap();
362    /// ```
363    pub fn repeat<T: ArrayElement>(array: Array, count: i32) -> Result<Array> {
364        let stream = Stream::thread_local_or_default();
365        Array::try_from_op(|res| unsafe {
366            mlx_sys::mlx_repeat(res, array.as_ptr(), count, stream.as_ref().as_ptr())
367        })
368    }
369
370    /// Compatibility shim for [`repeat`].
371    #[deprecated(
372        since = "0.26.0",
373        note = "use `with_stream` or `with_device` around `repeat`"
374    )]
375    pub fn repeat_device<T: ArrayElement>(
376        array: Array,
377        count: i32,
378        stream: impl AsRef<Stream>,
379    ) -> Result<Array> {
380        crate::with_stream(stream.as_ref(), || Self::repeat::<T>(array, count))
381    }
382
383    /// An array with ones at and below the given diagonal and zeros elsewhere.
384    ///
385    /// # Params
386    ///
387    /// - n: number of rows in the output
388    /// - m: number of columns in the output -- equal to `n` if not specified
389    /// - k: index of the diagonal -- defaults to 0 if not specified
390    ///
391    /// # Example
392    ///
393    /// ```rust
394    /// use mlx_rs::Array;
395    /// // [5, 5] array with the lower triangle filled with 1s
396    /// let r = Array::tri::<f32>(5, None, None);
397    /// ```
398    pub fn tri<T: ArrayElement>(n: i32, m: Option<i32>, k: Option<i32>) -> Result<Array> {
399        let stream = Stream::thread_local_or_default();
400        Array::try_from_op(|res| unsafe {
401            mlx_sys::mlx_tri(
402                res,
403                n,
404                m.unwrap_or(n),
405                k.unwrap_or(0),
406                T::DTYPE.into(),
407                stream.as_ref().as_ptr(),
408            )
409        })
410    }
411
412    /// Compatibility shim for [`tri`].
413    #[deprecated(
414        since = "0.26.0",
415        note = "use `with_stream` or `with_device` around `tri`"
416    )]
417    pub fn tri_device<T: ArrayElement>(
418        n: i32,
419        m: Option<i32>,
420        k: Option<i32>,
421        stream: impl AsRef<Stream>,
422    ) -> Result<Array> {
423        crate::with_stream(stream.as_ref(), || Self::tri::<T>(n, m, k))
424    }
425}
426
427/// See [`Array::zeros`]
428pub fn zeros<T: ArrayElement>(shape: &[i32]) -> Result<Array> {
429    Array::zeros::<T>(shape)
430}
431
432/// Compatibility shim for [`zeros`].
433#[generate_macro(customize(forwarding_shim = true))]
434#[deprecated(
435    since = "0.26.0",
436    note = "use `with_stream` or `with_device` around `zeros`"
437)]
438pub fn zeros_device<T: ArrayElement>(
439    shape: &[i32],
440    #[optional] stream: impl AsRef<Stream>,
441) -> Result<Array> {
442    crate::with_stream(stream.as_ref(), || zeros::<T>(shape))
443}
444
445/// An array of zeros like the input.
446pub fn zeros_like(input: impl AsRef<Array>) -> Result<Array> {
447    let a = input.as_ref();
448    let shape = a.shape();
449    let dtype = a.dtype();
450    zeros_dtype(shape, dtype)
451}
452
453/// Compatibility shim for [`zeros_like`].
454#[generate_macro(customize(forwarding_shim = true))]
455#[deprecated(
456    since = "0.26.0",
457    note = "use `with_stream` or `with_device` around `zeros_like`"
458)]
459pub fn zeros_like_device(
460    input: impl AsRef<Array>,
461    #[optional] stream: impl AsRef<Stream>,
462) -> Result<Array> {
463    crate::with_stream(stream.as_ref(), || zeros_like(input))
464}
465
466/// Similar to [`Array::zeros`] but with a specified dtype.
467pub fn zeros_dtype(shape: &[i32], dtype: Dtype) -> Result<Array> {
468    let stream = Stream::thread_local_or_default();
469    Array::try_from_op(|res| unsafe {
470        mlx_sys::mlx_zeros(
471            res,
472            shape.as_ptr(),
473            shape.len(),
474            dtype.into(),
475            stream.as_ref().as_ptr(),
476        )
477    })
478}
479
480/// Compatibility shim for [`zeros_dtype`].
481#[generate_macro(customize(forwarding_shim = true))]
482#[deprecated(
483    since = "0.26.0",
484    note = "use `with_stream` or `with_device` around `zeros_dtype`"
485)]
486pub fn zeros_dtype_device(
487    shape: &[i32],
488    dtype: Dtype,
489    #[optional] stream: impl AsRef<Stream>,
490) -> Result<Array> {
491    crate::with_stream(stream.as_ref(), || zeros_dtype(shape, dtype))
492}
493
494/// See [`Array::ones`]
495pub fn ones<T: ArrayElement>(shape: &[i32]) -> Result<Array> {
496    Array::ones::<T>(shape)
497}
498
499/// Compatibility shim for [`ones`].
500#[generate_macro(customize(forwarding_shim = true))]
501#[deprecated(
502    since = "0.26.0",
503    note = "use `with_stream` or `with_device` around `ones`"
504)]
505pub fn ones_device<T: ArrayElement>(
506    shape: &[i32],
507    #[optional] stream: impl AsRef<Stream>,
508) -> Result<Array> {
509    crate::with_stream(stream.as_ref(), || ones::<T>(shape))
510}
511
512/// An array of ones like the input.
513pub fn ones_like(input: impl AsRef<Array>) -> Result<Array> {
514    let a = input.as_ref();
515    let shape = a.shape();
516    let dtype = a.dtype();
517    ones_dtype(shape, dtype)
518}
519
520/// Compatibility shim for [`ones_like`].
521#[generate_macro(customize(forwarding_shim = true))]
522#[deprecated(
523    since = "0.26.0",
524    note = "use `with_stream` or `with_device` around `ones_like`"
525)]
526pub fn ones_like_device(
527    input: impl AsRef<Array>,
528    #[optional] stream: impl AsRef<Stream>,
529) -> Result<Array> {
530    crate::with_stream(stream.as_ref(), || ones_like(input))
531}
532
533/// An array filled with the given value, with the same shape as the input.
534///
535/// # Params
536///
537/// - `input`: Input array to take shape from
538/// - `values`: Value(s) to fill the array with
539/// - `dtype`: Optional dtype for the output array. Defaults to the dtype of the input array.
540/// - `stream`: Stream to run the operation on
541///
542/// # Example
543///
544/// ```rust
545/// use mlx_rs::{Array, Dtype, ops::full_like};
546///
547/// let a = Array::from_slice(&[1i32, 2, 3], &[3]);
548/// // Fill with same dtype as input
549/// let b = full_like(&a, &Array::from_f32(7.0), None).unwrap();
550/// assert_eq!(b.dtype(), Dtype::Int32);
551///
552/// // Fill with specified dtype
553/// let c = full_like(&a, &Array::from_f32(7.5), Some(Dtype::Float32)).unwrap();
554/// assert_eq!(c.dtype(), Dtype::Float32);
555/// ```
556pub fn full_like(
557    input: impl AsRef<Array>,
558    values: impl AsRef<Array>,
559    dtype: impl Into<Option<Dtype>>,
560) -> Result<Array> {
561    let stream = Stream::thread_local_or_default();
562    let a = input.as_ref();
563    let dtype = dtype.into().unwrap_or_else(|| a.dtype());
564    Array::try_from_op(|res| unsafe {
565        mlx_sys::mlx_full_like(
566            res,
567            a.as_ptr(),
568            values.as_ref().as_ptr(),
569            dtype.into(),
570            stream.as_ref().as_ptr(),
571        )
572    })
573}
574
575/// Compatibility shim for [`full_like`].
576#[generate_macro(customize(forwarding_shim = true))]
577#[deprecated(
578    since = "0.26.0",
579    note = "use `with_stream` or `with_device` around `full_like`"
580)]
581pub fn full_like_device(
582    input: impl AsRef<Array>,
583    values: impl AsRef<Array>,
584    #[optional] dtype: impl Into<Option<Dtype>>,
585    #[optional] stream: impl AsRef<Stream>,
586) -> Result<Array> {
587    crate::with_stream(stream.as_ref(), || full_like(input, values, dtype))
588}
589
590/// Similar to [`Array::ones`] but with a specified dtype.
591pub fn ones_dtype(shape: &[i32], dtype: Dtype) -> Result<Array> {
592    let stream = Stream::thread_local_or_default();
593    Array::try_from_op(|res| unsafe {
594        mlx_sys::mlx_ones(
595            res,
596            shape.as_ptr(),
597            shape.len(),
598            dtype.into(),
599            stream.as_ref().as_ptr(),
600        )
601    })
602}
603
604/// Compatibility shim for [`ones_dtype`].
605#[generate_macro(customize(forwarding_shim = true))]
606#[deprecated(
607    since = "0.26.0",
608    note = "use `with_stream` or `with_device` around `ones_dtype`"
609)]
610pub fn ones_dtype_device(
611    shape: &[i32],
612    dtype: Dtype,
613    #[optional] stream: impl AsRef<Stream>,
614) -> Result<Array> {
615    crate::with_stream(stream.as_ref(), || ones_dtype(shape, dtype))
616}
617
618/// See [`Array::eye`]
619pub fn eye<T: ArrayElement>(n: i32, m: Option<i32>, k: Option<i32>) -> Result<Array> {
620    Array::eye::<T>(n, m, k)
621}
622
623/// Compatibility shim for [`eye`].
624#[generate_macro(customize(forwarding_shim = true))]
625#[deprecated(
626    since = "0.26.0",
627    note = "use `with_stream` or `with_device` around `eye`"
628)]
629pub fn eye_device<T: ArrayElement>(
630    n: i32,
631    #[optional] m: Option<i32>,
632    #[optional] k: Option<i32>,
633    #[optional] stream: impl AsRef<Stream>,
634) -> Result<Array> {
635    crate::with_stream(stream.as_ref(), || eye::<T>(n, m, k))
636}
637
638/// See [`Array::full`]
639pub fn full<T: ArrayElement>(shape: &[i32], values: impl AsRef<Array>) -> Result<Array> {
640    Array::full::<T>(shape, values)
641}
642
643/// Compatibility shim for [`full`].
644#[generate_macro(customize(forwarding_shim = true))]
645#[deprecated(
646    since = "0.26.0",
647    note = "use `with_stream` or `with_device` around `full`"
648)]
649pub fn full_device<T: ArrayElement>(
650    shape: &[i32],
651    values: impl AsRef<Array>,
652    #[optional] stream: impl AsRef<Stream>,
653) -> Result<Array> {
654    crate::with_stream(stream.as_ref(), || full::<T>(shape, values))
655}
656
657/// See [`Array::identity`]
658pub fn identity<T: ArrayElement>(n: i32) -> Result<Array> {
659    Array::identity::<T>(n)
660}
661
662/// Compatibility shim for [`identity`].
663#[generate_macro(customize(forwarding_shim = true))]
664#[deprecated(
665    since = "0.26.0",
666    note = "use `with_stream` or `with_device` around `identity`"
667)]
668pub fn identity_device<T: ArrayElement>(
669    n: i32,
670    #[optional] stream: impl AsRef<Stream>,
671) -> Result<Array> {
672    crate::with_stream(stream.as_ref(), || identity::<T>(n))
673}
674
675/// See [`Array::arange`]
676pub fn arange<U, T>(
677    start: impl Into<Option<U>>,
678    stop: U,
679    step: impl Into<Option<U>>,
680) -> Result<Array>
681where
682    U: NumCast,
683    T: ArrayElement,
684{
685    Array::arange::<U, T>(start, stop, step)
686}
687
688/// Compatibility shim for [`arange`].
689#[generate_macro(customize(forwarding_shim = true))]
690#[deprecated(
691    since = "0.26.0",
692    note = "use `with_stream` or `with_device` around `arange`"
693)]
694pub fn arange_device<U, T>(
695    #[optional] start: impl Into<Option<U>>,
696    #[named] stop: U,
697    #[optional] step: impl Into<Option<U>>,
698    #[optional] stream: impl AsRef<Stream>,
699) -> Result<Array>
700where
701    U: NumCast,
702    T: ArrayElement,
703{
704    crate::with_stream(stream.as_ref(), || arange::<U, T>(start, stop, step))
705}
706
707/// Generate evenly spaced values between `start` and `stop`.
708///
709/// A negative count errors, zero returns an empty array, and one returns `start`. When `endpoint`
710/// is false, the interval is divided by `count` and `stop` is excluded. Endpoints are converted
711/// directly to the C ABI's `double` representation.
712///
713/// ```rust
714/// use mlx_rs::{ops::{linspace, LinspaceOptions}, with_stream, Stream};
715///
716/// // float64 runs on the CPU only.
717/// let output = with_stream(&Stream::cpu(), || {
718///     linspace::<_, f64>(
719///         16_777_217.0_f64,
720///         16_777_219.0_f64,
721///         LinspaceOptions {
722///             count: 3,
723///             endpoint: true,
724///         },
725///     )
726/// })
727/// .unwrap();
728/// assert_eq!(output.shape(), &[3]);
729/// ```
730pub fn linspace<U, T>(start: U, stop: U, options: LinspaceOptions) -> Result<Array>
731where
732    U: NumCast,
733    T: ArrayElement,
734{
735    let stream = Stream::thread_local_or_default();
736    let start: f64 = NumCast::from(start).unwrap();
737    let stop: f64 = NumCast::from(stop).unwrap();
738    Array::try_from_op(|res| unsafe {
739        mlx_sys::mlx_linspace_endpoint(
740            res,
741            start,
742            stop,
743            options.count,
744            options.endpoint,
745            T::DTYPE.into(),
746            stream.as_ref().as_ptr(),
747        )
748    })
749}
750
751/// Compatibility shim for [`linspace`].
752#[generate_macro(customize(forwarding_shim = true))]
753#[deprecated(
754    since = "0.26.0",
755    note = "use `with_stream` or `with_device` around `linspace`"
756)]
757pub fn linspace_device<U, T>(
758    start: U,
759    stop: U,
760    #[optional] count: impl Into<Option<i32>>,
761    #[optional] stream: impl AsRef<Stream>,
762) -> Result<Array>
763where
764    U: NumCast,
765    T: ArrayElement,
766{
767    crate::with_stream(stream.as_ref(), || {
768        linspace::<U, T>(
769            start,
770            stop,
771            LinspaceOptions {
772                count: count.into().unwrap_or(50),
773                endpoint: true,
774            },
775        )
776    })
777}
778
779/// See [`Array::repeat`]
780pub fn repeat_axis<T: ArrayElement>(array: Array, count: i32, axis: i32) -> Result<Array> {
781    Array::repeat_axis::<T>(array, count, axis)
782}
783
784/// Compatibility shim for [`repeat_axis`].
785#[generate_macro(customize(forwarding_shim = true))]
786#[deprecated(
787    since = "0.26.0",
788    note = "use `with_stream` or `with_device` around `repeat_axis`"
789)]
790pub fn repeat_axis_device<T: ArrayElement>(
791    array: Array,
792    count: i32,
793    axis: i32,
794    #[optional] stream: impl AsRef<Stream>,
795) -> Result<Array> {
796    crate::with_stream(stream.as_ref(), || repeat_axis::<T>(array, count, axis))
797}
798
799/// See [`Array::repeat`]
800pub fn repeat<T: ArrayElement>(array: Array, count: i32) -> Result<Array> {
801    Array::repeat::<T>(array, count)
802}
803
804/// Compatibility shim for [`repeat`].
805#[generate_macro(customize(forwarding_shim = true))]
806#[deprecated(
807    since = "0.26.0",
808    note = "use `with_stream` or `with_device` around `repeat`"
809)]
810pub fn repeat_device<T: ArrayElement>(
811    array: Array,
812    count: i32,
813    #[optional] stream: impl AsRef<Stream>,
814) -> Result<Array> {
815    crate::with_stream(stream.as_ref(), || repeat::<T>(array, count))
816}
817
818/// See [`Array::tri`]
819pub fn tri<T: ArrayElement>(n: i32, m: Option<i32>, k: Option<i32>) -> Result<Array> {
820    Array::tri::<T>(n, m, k)
821}
822
823/// Compatibility shim for [`tri`].
824#[generate_macro(customize(forwarding_shim = true))]
825#[deprecated(
826    since = "0.26.0",
827    note = "use `with_stream` or `with_device` around `tri`"
828)]
829pub fn tri_device<T: ArrayElement>(
830    n: i32,
831    #[optional] m: Option<i32>,
832    #[optional] k: Option<i32>,
833    #[optional] stream: impl AsRef<Stream>,
834) -> Result<Array> {
835    crate::with_stream(stream.as_ref(), || tri::<T>(n, m, k))
836}
837
838/// Zeros the array above the given diagonal
839///
840/// # Params
841///
842/// - `a`: input array
843/// - `k`: diagonal of the 2D array. Default to `0`
844/// - `stream`: stream to execute on
845pub fn tril(a: impl AsRef<Array>, k: impl Into<Option<i32>>) -> Result<Array> {
846    let stream = Stream::thread_local_or_default();
847    let a = a.as_ref();
848    let k = k.into().unwrap_or(0);
849    Array::try_from_op(|res| unsafe {
850        mlx_sys::mlx_tril(res, a.as_ptr(), k, stream.as_ref().as_ptr())
851    })
852}
853
854/// Compatibility shim for [`tril`].
855#[generate_macro(customize(forwarding_shim = true))]
856#[deprecated(
857    since = "0.26.0",
858    note = "use `with_stream` or `with_device` around `tril`"
859)]
860pub fn tril_device(
861    a: impl AsRef<Array>,
862    #[optional] k: impl Into<Option<i32>>,
863    #[optional] stream: impl AsRef<Stream>,
864) -> Result<Array> {
865    crate::with_stream(stream.as_ref(), || tril(a, k))
866}
867
868/// Zeros the array below the given diagonal
869///
870/// # Params
871///
872/// - `a`: input array
873/// - `k`: diagonal of the 2D array. Default to `0`
874pub fn triu(a: impl AsRef<Array>, k: impl Into<Option<i32>>) -> Result<Array> {
875    let stream = Stream::thread_local_or_default();
876    let a = a.as_ref();
877    let k = k.into().unwrap_or(0);
878    Array::try_from_op(|res| unsafe {
879        mlx_sys::mlx_triu(res, a.as_ptr(), k, stream.as_ref().as_ptr())
880    })
881}
882
883/// Compatibility shim for [`triu`].
884#[generate_macro(customize(forwarding_shim = true))]
885#[deprecated(
886    since = "0.26.0",
887    note = "use `with_stream` or `with_device` around `triu`"
888)]
889pub fn triu_device(
890    a: impl AsRef<Array>,
891    #[optional] k: impl Into<Option<i32>>,
892    #[optional] stream: impl AsRef<Stream>,
893) -> Result<Array> {
894    crate::with_stream(stream.as_ref(), || triu(a, k))
895}
896
897#[cfg(test)]
898mod tests {
899    use super::*;
900    use crate::{
901        array, dtype::Dtype, test_utils::assert_array_eq, test_utils::tolerances, with_device,
902        Device,
903    };
904    use half::f16;
905
906    #[test]
907    fn test_zeros() {
908        let array = Array::zeros::<f32>(&[2, 3]).unwrap();
909        assert_eq!(array.shape(), &[2, 3]);
910        assert_eq!(array.dtype(), Dtype::Float32);
911
912        let data: &[f32] = array.as_slice();
913        assert_eq!(data, &[0.0; 6]);
914    }
915
916    #[test]
917    fn test_zeros_try() {
918        let array = Array::zeros::<f32>(&[2, 3]);
919        assert!(array.is_ok());
920
921        let array = Array::zeros::<f32>(&[-1, 3]);
922        assert!(array.is_err());
923    }
924
925    #[test]
926    fn test_ones() {
927        let array = Array::ones::<f16>(&[2, 3]).unwrap();
928        assert_eq!(array.shape(), &[2, 3]);
929        assert_eq!(array.dtype(), Dtype::Float16);
930
931        let data: &[f16] = array.as_slice();
932        assert_eq!(data, &[f16::from_f32(1.0); 6]);
933    }
934
935    #[test]
936    fn test_eye() {
937        let array = Array::eye::<f32>(3, None, None).unwrap();
938        assert_eq!(array.shape(), &[3, 3]);
939        assert_eq!(array.dtype(), Dtype::Float32);
940
941        let data: &[f32] = array.as_slice();
942        assert_eq!(data, &[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
943    }
944
945    #[test]
946    fn test_full_scalar() {
947        let array = Array::full::<f32>(&[2, 3], array!(7f32)).unwrap();
948        assert_eq!(array.shape(), &[2, 3]);
949        assert_eq!(array.dtype(), Dtype::Float32);
950
951        assert_array_eq(
952            array,
953            Array::from_slice(&[7.0; 6], &[2, 3]),
954            tolerances::EXACT.rtol,
955            tolerances::EXACT.atol,
956        );
957    }
958
959    #[test]
960    fn test_full_array() {
961        let source = with_device(Device::cpu(), || Array::zeros::<f32>(&[1, 3])).unwrap();
962        let array = Array::full::<f32>(&[2, 3], source).unwrap();
963        assert_eq!(array.shape(), &[2, 3]);
964        assert_eq!(array.dtype(), Dtype::Float32);
965
966        assert_array_eq(
967            array,
968            Array::from_slice(&[0.0; 6], &[2, 3]),
969            tolerances::EXACT.rtol,
970            tolerances::EXACT.atol,
971        );
972    }
973
974    #[test]
975    fn test_full_try() {
976        let source = Array::zeros::<f32>(&[1, 3]).unwrap();
977        let array = Array::full::<f32>(&[2, 3], source);
978        assert!(array.is_ok());
979
980        let source = Array::zeros::<f32>(&[1, 3]).unwrap();
981        let array = Array::full::<f32>(&[-1, 3], source);
982        assert!(array.is_err());
983    }
984
985    #[test]
986    fn test_identity() {
987        let array = Array::identity::<f32>(3).unwrap();
988        assert_eq!(array.shape(), &[3, 3]);
989        assert_eq!(array.dtype(), Dtype::Float32);
990
991        let data: &[f32] = array.as_slice();
992        assert_eq!(data, &[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
993    }
994
995    #[test]
996    fn test_arange() {
997        let array = Array::arange::<_, f32>(None, 50, None).unwrap();
998        assert_eq!(array.shape(), &[50]);
999        assert_eq!(array.dtype(), Dtype::Float32);
1000
1001        let data: &[f32] = array.as_slice();
1002        let expected: Vec<f32> = (0..50).map(|x| x as f32).collect();
1003        assert_eq!(data, expected.as_slice());
1004
1005        let array = Array::arange::<_, i32>(0, 50, None).unwrap();
1006        assert_eq!(array.shape(), &[50]);
1007        assert_eq!(array.dtype(), Dtype::Int32);
1008
1009        let data: &[i32] = array.as_slice();
1010        let expected: Vec<i32> = (0..50).collect();
1011        assert_eq!(data, expected.as_slice());
1012
1013        let result = Array::arange::<_, bool>(None, 50, None);
1014        assert!(result.is_err());
1015
1016        let result = Array::arange::<_, f32>(f64::NEG_INFINITY, 50.0, None);
1017        assert!(result.is_err());
1018
1019        let result = Array::arange::<_, f32>(0.0, f64::INFINITY, None);
1020        assert!(result.is_err());
1021
1022        let result = Array::arange::<_, f32>(0.0, 50.0, f32::NAN);
1023        assert!(result.is_err());
1024
1025        let result = Array::arange::<_, f32>(f32::NAN, 50.0, None);
1026        assert!(result.is_err());
1027
1028        let result = Array::arange::<_, f32>(0.0, f32::NAN, None);
1029        assert!(result.is_err());
1030
1031        let result = Array::arange::<_, f32>(0, i32::MAX as i64 + 1, None);
1032        assert!(result.is_err());
1033    }
1034
1035    #[test]
1036    fn test_linspace_int() {
1037        let array = Array::linspace::<_, f32>(0, 50, None).unwrap();
1038        assert_eq!(array.shape(), &[50]);
1039        assert_eq!(array.dtype(), Dtype::Float32);
1040
1041        let expected_data: Vec<f32> = (0..50).map(|x| x as f32 * (50.0 / 49.0)).collect();
1042        let expected = Array::from_slice(&expected_data, &[50]);
1043        assert_eq!(array.shape(), expected.shape());
1044        assert_array_all_close!(array, expected);
1045    }
1046
1047    #[test]
1048    fn test_linspace_float() {
1049        let array = Array::linspace::<_, f32>(0., 50., None).unwrap();
1050        assert_eq!(array.shape(), &[50]);
1051        assert_eq!(array.dtype(), Dtype::Float32);
1052
1053        let expected_data: Vec<f32> = (0..50).map(|x| x as f32 * (50.0 / 49.0)).collect();
1054        let expected = Array::from_slice(&expected_data, &[50]);
1055        assert_eq!(array.shape(), expected.shape());
1056        assert_array_all_close!(array, expected);
1057    }
1058
1059    #[test]
1060    fn test_linspace_try() {
1061        let array = Array::linspace::<_, f32>(0, 50, None);
1062        assert!(array.is_ok());
1063
1064        let array = Array::linspace::<_, f32>(0, 50, Some(-1));
1065        assert!(array.is_err());
1066    }
1067
1068    #[test]
1069    fn test_repeat() {
1070        let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
1071        let array = Array::repeat_axis::<i32>(source, 4, 1).unwrap();
1072        assert_eq!(array.shape(), &[2, 8]);
1073        assert_eq!(array.dtype(), Dtype::Int32);
1074
1075        let data: &[i32] = array.as_slice();
1076        assert_eq!(data, [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]);
1077    }
1078
1079    #[test]
1080    fn test_repeat_try() {
1081        let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
1082        let array = Array::repeat_axis::<i32>(source, 4, 1);
1083        assert!(array.is_ok());
1084
1085        let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
1086        let array = Array::repeat_axis::<i32>(source, -1, 1);
1087        assert!(array.is_err());
1088    }
1089
1090    #[test]
1091    fn test_repeat_all() {
1092        let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
1093        let array = Array::repeat::<i32>(source, 4).unwrap();
1094        assert_eq!(array.shape(), &[16]);
1095        assert_eq!(array.dtype(), Dtype::Int32);
1096
1097        let data: &[i32] = array.as_slice();
1098        assert_eq!(data, [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]);
1099    }
1100
1101    #[test]
1102    fn test_repeat_all_try() {
1103        let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
1104        let array = Array::repeat::<i32>(source, 4);
1105        assert!(array.is_ok());
1106
1107        let source = Array::from_slice(&[0, 1, 2, 3], &[2, 2]);
1108        let array = Array::repeat::<i32>(source, -1);
1109        assert!(array.is_err());
1110    }
1111
1112    #[test]
1113    fn test_tri() {
1114        let array = Array::tri::<f32>(3, None, None).unwrap();
1115        assert_eq!(array.shape(), &[3, 3]);
1116        assert_eq!(array.dtype(), Dtype::Float32);
1117
1118        let data: &[f32] = array.as_slice();
1119        assert_eq!(data, &[1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0]);
1120    }
1121
1122    // The tests below are adapted from the C++ unit test `ops_tests.cpp/test full_like`
1123    #[test]
1124    fn test_full_like() {
1125        // Test with explicit dtype (different from input)
1126        let base_int = Array::from_slice(&[1i16, 2, 3], &[3]);
1127        let from_array_with_dtype =
1128            full_like(&base_int, &array!(7.5f32), Some(Dtype::Float16)).unwrap();
1129        assert_eq!(from_array_with_dtype.dtype(), Dtype::Float16);
1130        assert_eq!(from_array_with_dtype.shape(), &[3]);
1131
1132        assert_array_eq(
1133            from_array_with_dtype,
1134            Array::from_slice(&[f16::from_f32(7.5); 3], &[3]),
1135            tolerances::EXACT.rtol,
1136            tolerances::EXACT.atol,
1137        );
1138
1139        // Test with default dtype (inherits from input)
1140        let from_array_default_dtype = full_like(&base_int, &array!(4.0f32), None).unwrap();
1141        assert_eq!(from_array_default_dtype.dtype(), Dtype::Int16);
1142        assert_array_eq(
1143            from_array_default_dtype,
1144            Array::from_slice(&[4_i16, 4, 4], &[3]),
1145            tolerances::EXACT.rtol,
1146            tolerances::EXACT.atol,
1147        );
1148
1149        // Test with explicit dtype float32
1150        let from_scalar_with_dtype =
1151            full_like(&base_int, &array!(3.25f32), Some(Dtype::Float32)).unwrap();
1152        assert_eq!(from_scalar_with_dtype.dtype(), Dtype::Float32);
1153        assert_array_eq(
1154            from_scalar_with_dtype,
1155            Array::from_slice(&[3.25_f32; 3], &[3]),
1156            tolerances::EXACT.rtol,
1157            tolerances::EXACT.atol,
1158        );
1159
1160        // Test with float base and int value - uses base dtype
1161        let base_float = Array::from_slice(&[1.0f32, 2.0f32], &[2]);
1162        let from_scalar_default_dtype = full_like(&base_float, &array!(2i32), None).unwrap();
1163        assert_eq!(from_scalar_default_dtype.dtype(), Dtype::Float32);
1164        assert_array_eq(
1165            from_scalar_default_dtype,
1166            Array::from_slice(&[2.0_f32; 2], &[2]),
1167            tolerances::EXACT.rtol,
1168            tolerances::EXACT.atol,
1169        );
1170    }
1171}