Skip to main content

mlx_rs/ops/
arithmetic.rs

1use crate::array::Array;
2use crate::error::Result;
3use crate::sealed::Sealed;
4
5use crate::utils::guard::Guarded;
6use crate::utils::{IntoOption, ScalarOrArray, VectorArray};
7use crate::Stream;
8use mlx_internal_macros::generate_macro;
9use smallvec::SmallVec;
10
11impl Array {
12    /// Apply adjacent subtraction `n` times along `axis`.
13    ///
14    /// `n == 0` returns an identity result. MLX does not provide prepend or append operands for
15    /// this operation.
16    ///
17    /// ```rust
18    /// use mlx_rs::array;
19    ///
20    /// let output = array!([1, 4, 9]).diff(1, -1).unwrap();
21    /// assert_eq!(output.shape(), &[2]);
22    /// ```
23    pub fn diff(&self, n: i32, axis: i32) -> Result<Array> {
24        let stream = Stream::thread_local_or_default();
25        Array::try_from_op(|res| unsafe {
26            mlx_sys::mlx_diff(res, self.as_ptr(), n, axis, stream.as_ref().as_ptr())
27        })
28    }
29
30    /// Round floating-point values toward zero.
31    ///
32    /// Signed zero is preserved, integers are unchanged, and complex inputs are rejected.
33    ///
34    /// ```rust
35    /// use mlx_rs::array;
36    ///
37    /// let output = array!([-1.75, 2.25]).trunc().unwrap();
38    /// assert_eq!(output.shape(), &[2]);
39    /// ```
40    pub fn trunc(&self) -> Result<Array> {
41        let stream = Stream::thread_local_or_default();
42        Array::try_from_op(|res| unsafe {
43            mlx_sys::mlx_trunc(res, self.as_ptr(), stream.as_ref().as_ptr())
44        })
45    }
46
47    /// Element-wise absolute value.
48    ///
49    /// # Example
50    ///
51    /// ```rust
52    /// use mlx_rs::Array;
53    /// let array = Array::from_slice(&[1i32, 2, -3, -4, -5], &[5]);
54    /// let mut result = array.abs().unwrap();
55    ///
56    /// let data: &[i32] = result.as_slice();
57    /// // data == [1, 2, 3, 4, 5]
58    /// ```
59    pub fn abs(&self) -> Result<Array> {
60        let stream = Stream::thread_local_or_default();
61        Array::try_from_op(|res| unsafe {
62            mlx_sys::mlx_abs(res, self.as_ptr(), stream.as_ref().as_ptr())
63        })
64    }
65
66    /// Compatibility shim for [`abs`].
67    #[deprecated(
68        since = "0.26.0",
69        note = "use `with_stream` or `with_device` around `abs`"
70    )]
71    pub fn abs_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
72        crate::with_stream(stream.as_ref(), || self.abs())
73    }
74
75    /// Element-wise addition returning an error if arrays are not broadcastable.
76    ///
77    /// Add two arrays with [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
78    ///
79    /// # Params
80    ///
81    /// - other: array to add
82    ///
83    /// # Example
84    ///
85    /// ```rust
86    /// use mlx_rs::Array;
87    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
88    /// let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
89    /// let mut c = a.add(&b).unwrap();
90    ///
91    /// let c_data: &[f32] = c.as_slice();
92    /// // c_data == [5.0, 7.0, 9.0]
93    /// ```
94    pub fn add(&self, other: impl AsRef<Array>) -> Result<Array> {
95        let stream = Stream::thread_local_or_default();
96        Array::try_from_op(|res| unsafe {
97            mlx_sys::mlx_add(
98                res,
99                self.as_ptr(),
100                other.as_ref().as_ptr(),
101                stream.as_ref().as_ptr(),
102            )
103        })
104    }
105
106    /// Compatibility shim for [`add`].
107    #[deprecated(
108        since = "0.26.0",
109        note = "use `with_stream` or `with_device` around `add`"
110    )]
111    pub fn add_device(
112        &self,
113        other: impl AsRef<Array>,
114        stream: impl AsRef<Stream>,
115    ) -> Result<Array> {
116        crate::with_stream(stream.as_ref(), || self.add(other))
117    }
118
119    /// Element-wise subtraction returning an error if arrays are not broadcastable.
120    ///
121    /// Subtract two arrays with [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
122    ///
123    /// # Params
124    ///
125    /// - other: array to subtract
126    ///
127    /// # Example
128    ///
129    /// ```rust
130    /// use mlx_rs::Array;
131    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
132    /// let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
133    /// let mut c = a.subtract(&b).unwrap();
134    ///
135    /// let c_data: &[f32] = c.as_slice();
136    /// // c_data == [-3.0, -3.0, -3.0]
137    /// ```
138    pub fn subtract(&self, other: impl AsRef<Array>) -> Result<Array> {
139        let stream = Stream::thread_local_or_default();
140        Array::try_from_op(|res| unsafe {
141            mlx_sys::mlx_subtract(
142                res,
143                self.as_ptr(),
144                other.as_ref().as_ptr(),
145                stream.as_ref().as_ptr(),
146            )
147        })
148    }
149
150    /// Compatibility shim for [`subtract`].
151    #[deprecated(
152        since = "0.26.0",
153        note = "use `with_stream` or `with_device` around `subtract`"
154    )]
155    pub fn subtract_device(
156        &self,
157        other: impl AsRef<Array>,
158        stream: impl AsRef<Stream>,
159    ) -> Result<Array> {
160        crate::with_stream(stream.as_ref(), || self.subtract(other))
161    }
162
163    /// Unary element-wise negation. Returns an error if the array is of type bool.
164    ///
165    /// Negate the values in the array.
166    ///
167    /// # Example
168    ///
169    /// ```rust
170    /// use mlx_rs::Array;
171    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
172    /// let mut b = a.negative().unwrap();
173    ///
174    /// let b_data: &[f32] = b.as_slice();
175    /// // b_data == [-1.0, -2.0, -3.0]
176    /// ```
177    pub fn negative(&self) -> Result<Array> {
178        let stream = Stream::thread_local_or_default();
179        Array::try_from_op(|res| unsafe {
180            mlx_sys::mlx_negative(res, self.as_ptr(), stream.as_ref().as_ptr())
181        })
182    }
183
184    /// Compatibility shim for [`negative`].
185    #[deprecated(
186        since = "0.26.0",
187        note = "use `with_stream` or `with_device` around `negative`"
188    )]
189    pub fn negative_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
190        crate::with_stream(stream.as_ref(), || self.negative())
191    }
192
193    /// Element-wise multiplication returning an error if arrays are not broadcastable.
194    ///
195    /// Multiply two arrays with [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
196    ///
197    /// # Example
198    ///
199    /// ```rust
200    /// use mlx_rs::Array;
201    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
202    /// let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
203    /// let mut c = a.multiply(&b).unwrap();
204    ///
205    /// let c_data: &[f32] = c.as_slice();
206    /// // c_data == [4.0, 10.0, 18.0]
207    /// ```
208    pub fn multiply(&self, other: impl AsRef<Array>) -> Result<Array> {
209        let stream = Stream::thread_local_or_default();
210        Array::try_from_op(|res| unsafe {
211            mlx_sys::mlx_multiply(
212                res,
213                self.as_ptr(),
214                other.as_ref().as_ptr(),
215                stream.as_ref().as_ptr(),
216            )
217        })
218    }
219
220    /// Compatibility shim for [`multiply`].
221    #[deprecated(
222        since = "0.26.0",
223        note = "use `with_stream` or `with_device` around `multiply`"
224    )]
225    pub fn multiply_device(
226        &self,
227        other: impl AsRef<Array>,
228        stream: impl AsRef<Stream>,
229    ) -> Result<Array> {
230        crate::with_stream(stream.as_ref(), || self.multiply(other))
231    }
232
233    /// Replace NaN and Inf values with finite numbers.
234    ///
235    /// # Params
236    /// - nan: value to replace NaN with
237    /// - posInf: value to replace positive inifinites with.  If not specified will use
238    ///   the largest finite value for the given dtype.
239    /// - negInf: value to replace negative inifinites with.  If not specified will use
240    ///   the negative of the largest finite value for the given dtype.
241    /// - stream: stream or device to evaluate on
242    pub fn nan_to_num(
243        &self,
244        nan: impl IntoOption<f32>,
245        pos_inf: impl IntoOption<f32>,
246        neg_inf: impl IntoOption<f32>,
247    ) -> Result<Array> {
248        let stream = Stream::thread_local_or_default();
249        let pos_inf = pos_inf.into_option();
250        let neg_inf = neg_inf.into_option();
251
252        let pos_inf = mlx_sys::mlx_optional_float {
253            value: pos_inf.unwrap_or(0.0),
254            has_value: pos_inf.is_some(),
255        };
256        let neg_inf = mlx_sys::mlx_optional_float {
257            value: neg_inf.unwrap_or(0.0),
258            has_value: neg_inf.is_some(),
259        };
260
261        Array::try_from_op(|res| unsafe {
262            mlx_sys::mlx_nan_to_num(
263                res,
264                self.as_ptr(),
265                nan.into_option().unwrap_or(0.),
266                pos_inf,
267                neg_inf,
268                stream.as_ref().as_ptr(),
269            )
270        })
271    }
272
273    /// Compatibility shim for [`nan_to_num`].
274    #[deprecated(
275        since = "0.26.0",
276        note = "use `with_stream` or `with_device` around `nan_to_num`"
277    )]
278    pub fn nan_to_num_device(
279        &self,
280        nan: impl IntoOption<f32>,
281        pos_inf: impl IntoOption<f32>,
282        neg_inf: impl IntoOption<f32>,
283        stream: impl AsRef<Stream>,
284    ) -> Result<Array> {
285        crate::with_stream(stream.as_ref(), || self.nan_to_num(nan, pos_inf, neg_inf))
286    }
287
288    /// Element-wise division returning an error if arrays are not broadcastable.
289    ///
290    /// Divide two arrays with [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
291    ///
292    /// # Params
293    ///
294    /// - other: array to divide
295    ///
296    /// # Example
297    ///
298    /// ```rust
299    /// use mlx_rs::Array;
300    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
301    /// let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
302    /// let mut c = a.divide(&b).unwrap();
303    ///
304    /// let c_data: &[f32] = c.as_slice();
305    /// // c_data == [0.25, 0.4, 0.5]
306    /// ```
307    pub fn divide(&self, other: impl AsRef<Array>) -> Result<Array> {
308        let stream = Stream::thread_local_or_default();
309        Array::try_from_op(|res| unsafe {
310            mlx_sys::mlx_divide(
311                res,
312                self.as_ptr(),
313                other.as_ref().as_ptr(),
314                stream.as_ref().as_ptr(),
315            )
316        })
317    }
318
319    /// Compatibility shim for [`divide`].
320    #[deprecated(
321        since = "0.26.0",
322        note = "use `with_stream` or `with_device` around `divide`"
323    )]
324    pub fn divide_device(
325        &self,
326        other: impl AsRef<Array>,
327        stream: impl AsRef<Stream>,
328    ) -> Result<Array> {
329        crate::with_stream(stream.as_ref(), || self.divide(other))
330    }
331
332    /// Element-wise power operation returning an error if arrays are not broadcastable if they have different shapes.
333    ///
334    /// Raise the elements of the array to the power of the elements of another array.
335    ///
336    /// # Params
337    ///
338    /// - other: array to raise to the power of
339    ///
340    /// # Example
341    ///
342    /// ```rust
343    /// use mlx_rs::Array;
344    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
345    /// let b = Array::from_slice(&[2.0, 3.0, 4.0], &[3]);
346    /// let mut c = a.power(&b).unwrap();
347    ///
348    /// let c_data: &[f32] = c.as_slice();
349    /// // c_data == [1.0, 8.0, 81.0]
350    /// ```
351    pub fn power(&self, other: impl AsRef<Array>) -> Result<Array> {
352        let stream = Stream::thread_local_or_default();
353        Array::try_from_op(|res| unsafe {
354            mlx_sys::mlx_power(
355                res,
356                self.as_ptr(),
357                other.as_ref().as_ptr(),
358                stream.as_ref().as_ptr(),
359            )
360        })
361    }
362
363    /// Compatibility shim for [`power`].
364    #[deprecated(
365        since = "0.26.0",
366        note = "use `with_stream` or `with_device` around `power`"
367    )]
368    pub fn power_device(
369        &self,
370        other: impl AsRef<Array>,
371        stream: impl AsRef<Stream>,
372    ) -> Result<Array> {
373        crate::with_stream(stream.as_ref(), || self.power(other))
374    }
375
376    /// Element-wise remainder of division returning an error if arrays are not broadcastable.
377    ///
378    /// Computes the remainder of dividing `lhs` with `rhs` with [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
379    ///
380    /// # Params
381    ///
382    /// - other: array to divide
383    ///
384    /// # Example
385    ///
386    /// ```rust
387    /// use mlx_rs::Array;
388    /// let a = Array::from_slice(&[10.0, 11.0, 12.0], &[3]);
389    /// let b = Array::from_slice(&[3.0, 4.0, 5.0], &[3]);
390    /// let mut c = a.remainder(&b).unwrap();
391    ///
392    /// let c_data: &[f32] = c.as_slice();
393    /// // c_data == [1.0, 3.0, 2.0]
394    /// ```
395    pub fn remainder(&self, other: impl AsRef<Array>) -> Result<Array> {
396        let stream = Stream::thread_local_or_default();
397        Array::try_from_op(|res| unsafe {
398            mlx_sys::mlx_remainder(
399                res,
400                self.as_ptr(),
401                other.as_ref().as_ptr(),
402                stream.as_ref().as_ptr(),
403            )
404        })
405    }
406
407    /// Compatibility shim for [`remainder`].
408    #[deprecated(
409        since = "0.26.0",
410        note = "use `with_stream` or `with_device` around `remainder`"
411    )]
412    pub fn remainder_device(
413        &self,
414        other: impl AsRef<Array>,
415        stream: impl AsRef<Stream>,
416    ) -> Result<Array> {
417        crate::with_stream(stream.as_ref(), || self.remainder(other))
418    }
419
420    /// Element-wise square root
421    ///
422    /// # Example
423    ///
424    /// ```rust
425    /// use mlx_rs::Array;
426    /// let a = Array::from_slice(&[1.0, 4.0, 9.0], &[3]);
427    /// let mut b = a.sqrt().unwrap();
428    ///
429    /// let b_data: &[f32] = b.as_slice();
430    /// // b_data == [1.0, 2.0, 3.0]
431    /// ```
432    pub fn sqrt(&self) -> Result<Array> {
433        let stream = Stream::thread_local_or_default();
434        Array::try_from_op(|res| unsafe {
435            mlx_sys::mlx_sqrt(res, self.as_ptr(), stream.as_ref().as_ptr())
436        })
437    }
438
439    /// Compatibility shim for [`sqrt`].
440    #[deprecated(
441        since = "0.26.0",
442        note = "use `with_stream` or `with_device` around `sqrt`"
443    )]
444    pub fn sqrt_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
445        crate::with_stream(stream.as_ref(), || self.sqrt())
446    }
447
448    /// Element-wise cosine
449    ///
450    /// # Example
451    ///
452    /// ```rust
453    /// use mlx_rs::Array;
454    /// let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
455    /// let mut b = a.cos().unwrap();
456    ///
457    /// let b_data: &[f32] = b.as_slice();
458    /// // b_data == [1.0, 0.54030234, -0.41614687]
459    /// ```
460    pub fn cos(&self) -> Result<Array> {
461        let stream = Stream::thread_local_or_default();
462        Array::try_from_op(|res| unsafe {
463            mlx_sys::mlx_cos(res, self.as_ptr(), stream.as_ref().as_ptr())
464        })
465    }
466
467    /// Compatibility shim for [`cos`].
468    #[deprecated(
469        since = "0.26.0",
470        note = "use `with_stream` or `with_device` around `cos`"
471    )]
472    pub fn cos_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
473        crate::with_stream(stream.as_ref(), || self.cos())
474    }
475
476    /// Element-wise exponential.
477    ///
478    /// # Example
479    ///
480    /// ```rust
481    /// use mlx_rs::Array;
482    ///
483    /// let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
484    /// let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
485    /// let mut b = a.exp().unwrap();
486    ///
487    /// let b_data: &[f32] = b.as_slice();
488    /// // b_data == [1.0, 2.7182817, 7.389056]
489    /// ```
490    pub fn exp(&self) -> Result<Array> {
491        let stream = Stream::thread_local_or_default();
492        Array::try_from_op(|res| unsafe {
493            mlx_sys::mlx_exp(res, self.as_ptr(), stream.as_ref().as_ptr())
494        })
495    }
496
497    /// Compatibility shim for [`exp`].
498    #[deprecated(
499        since = "0.26.0",
500        note = "use `with_stream` or `with_device` around `exp`"
501    )]
502    pub fn exp_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
503        crate::with_stream(stream.as_ref(), || self.exp())
504    }
505
506    /// Element-wise floor returning an error if the array is of type complex64.
507    ///
508    /// # Example
509    ///
510    /// ```rust
511    /// use mlx_rs::Array;
512    /// let a = Array::from_slice(&[0.1, 1.9, 2.5], &[3]);
513    /// let mut b = a.floor().unwrap();
514    ///
515    /// let b_data: &[f32] = b.as_slice();
516    /// // b_data == [0.0, 1.0, 2.0]
517    /// ```
518    pub fn floor(&self) -> Result<Array> {
519        let stream = Stream::thread_local_or_default();
520        Array::try_from_op(|res| unsafe {
521            mlx_sys::mlx_floor(res, self.as_ptr(), stream.as_ref().as_ptr())
522        })
523    }
524
525    /// Compatibility shim for [`floor`].
526    #[deprecated(
527        since = "0.26.0",
528        note = "use `with_stream` or `with_device` around `floor`"
529    )]
530    pub fn floor_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
531        crate::with_stream(stream.as_ref(), || self.floor())
532    }
533
534    /// Element-wise integer division returning an error if arrays are not broadcastable.
535    ///
536    /// Divide two arrays with
537    /// [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
538    ///
539    /// If either array is a floating point type then it is equivalent to calling [`Array::floor()`]
540    /// after `/`.
541    ///
542    /// # Params
543    ///
544    /// - other: array to divide
545    ///
546    /// # Example
547    ///
548    /// ```rust
549    /// use mlx_rs::Array;
550    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
551    /// let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
552    /// let mut c = a.floor_divide(&b).unwrap();
553    ///
554    /// let c_data: &[f32] = c.as_slice();
555    /// // c_data == [0.25, 0.4, 0.5]
556    /// ```
557    pub fn floor_divide(&self, other: impl AsRef<Array>) -> Result<Array> {
558        let stream = Stream::thread_local_or_default();
559        Array::try_from_op(|res| unsafe {
560            mlx_sys::mlx_floor_divide(
561                res,
562                self.as_ptr(),
563                other.as_ref().as_ptr(),
564                stream.as_ref().as_ptr(),
565            )
566        })
567    }
568
569    /// Compatibility shim for [`floor_divide`].
570    #[deprecated(
571        since = "0.26.0",
572        note = "use `with_stream` or `with_device` around `floor_divide`"
573    )]
574    pub fn floor_divide_device(
575        &self,
576        other: impl AsRef<Array>,
577        stream: impl AsRef<Stream>,
578    ) -> Result<Array> {
579        crate::with_stream(stream.as_ref(), || self.floor_divide(other))
580    }
581
582    /// Return a boolean array indicating which elements are NaN.
583    ///
584    /// # Params
585    /// - stream: stream or device to evaluate on
586    pub fn is_nan(&self) -> Result<Array> {
587        let stream = Stream::thread_local_or_default();
588        Array::try_from_op(|res| unsafe {
589            mlx_sys::mlx_isnan(res, self.as_ptr(), stream.as_ref().as_ptr())
590        })
591    }
592
593    /// Compatibility shim for [`is_nan`].
594    #[deprecated(
595        since = "0.26.0",
596        note = "use `with_stream` or `with_device` around `is_nan`"
597    )]
598    pub fn is_nan_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
599        crate::with_stream(stream.as_ref(), || self.is_nan())
600    }
601
602    /// Return a boolean array indicating which elements are infinity.
603    ///
604    /// # Params
605    /// - stream: stream or device to evaluate on
606    pub fn is_inf(&self) -> Result<Array> {
607        let stream = Stream::thread_local_or_default();
608        Array::try_from_op(|res| unsafe {
609            mlx_sys::mlx_isinf(res, self.as_ptr(), stream.as_ref().as_ptr())
610        })
611    }
612
613    /// Compatibility shim for [`is_inf`].
614    #[deprecated(
615        since = "0.26.0",
616        note = "use `with_stream` or `with_device` around `is_inf`"
617    )]
618    pub fn is_inf_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
619        crate::with_stream(stream.as_ref(), || self.is_inf())
620    }
621
622    /// Return a boolean array indicating which elements are finite.
623    ///
624    /// # Params
625    /// - stream: stream or device to evaluate on
626    pub fn is_finite(&self) -> Result<Array> {
627        let stream = Stream::thread_local_or_default();
628        Array::try_from_op(|res| unsafe {
629            mlx_sys::mlx_isfinite(res, self.as_ptr(), stream.as_ref().as_ptr())
630        })
631    }
632
633    /// Compatibility shim for [`is_finite`].
634    #[deprecated(
635        since = "0.26.0",
636        note = "use `with_stream` or `with_device` around `is_finite`"
637    )]
638    pub fn is_finite_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
639        crate::with_stream(stream.as_ref(), || self.is_finite())
640    }
641
642    /// Return a boolean array indicating which elements are negative infinity.
643    ///
644    /// # Params
645    /// - stream: stream or device to evaluate on
646    pub fn is_neg_inf(&self) -> Result<Array> {
647        let stream = Stream::thread_local_or_default();
648        Array::try_from_op(|res| unsafe {
649            mlx_sys::mlx_isneginf(res, self.as_ptr(), stream.as_ref().as_ptr())
650        })
651    }
652
653    /// Compatibility shim for [`is_neg_inf`].
654    #[deprecated(
655        since = "0.26.0",
656        note = "use `with_stream` or `with_device` around `is_neg_inf`"
657    )]
658    pub fn is_neg_inf_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
659        crate::with_stream(stream.as_ref(), || self.is_neg_inf())
660    }
661
662    /// Return a boolean array indicating which elements are positive infinity.
663    ///
664    /// # Params
665    /// - stream: stream or device to evaluate on
666    pub fn is_pos_inf(&self) -> Result<Array> {
667        let stream = Stream::thread_local_or_default();
668        Array::try_from_op(|res| unsafe {
669            mlx_sys::mlx_isposinf(res, self.as_ptr(), stream.as_ref().as_ptr())
670        })
671    }
672
673    /// Compatibility shim for [`is_pos_inf`].
674    #[deprecated(
675        since = "0.26.0",
676        note = "use `with_stream` or `with_device` around `is_pos_inf`"
677    )]
678    pub fn is_pos_inf_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
679        crate::with_stream(stream.as_ref(), || self.is_pos_inf())
680    }
681
682    /// Element-wise natural logarithm.
683    ///
684    /// # Example
685    ///
686    /// ```rust
687    /// use mlx_rs::Array;
688    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
689    /// let mut b = a.log().unwrap();
690    ///
691    /// let b_data: &[f32] = b.as_slice();
692    /// // b_data == [0.0, 0.6931472, 1.0986123]
693    /// ```
694    pub fn log(&self) -> Result<Array> {
695        let stream = Stream::thread_local_or_default();
696        Array::try_from_op(|res| unsafe {
697            mlx_sys::mlx_log(res, self.as_ptr(), stream.as_ref().as_ptr())
698        })
699    }
700
701    /// Compatibility shim for [`log`].
702    #[deprecated(
703        since = "0.26.0",
704        note = "use `with_stream` or `with_device` around `log`"
705    )]
706    pub fn log_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
707        crate::with_stream(stream.as_ref(), || self.log())
708    }
709
710    /// Element-wise base-2 logarithm.
711    ///
712    /// # Example
713    ///
714    /// ```rust
715    /// use mlx_rs::Array;
716    /// let a = Array::from_slice(&[1.0, 2.0, 4.0, 8.0], &[4]);
717    /// let mut b = a.log2().unwrap();
718    ///
719    /// let b_data: &[f32] = b.as_slice();
720    /// // b_data == [0.0, 1.0, 2.0, 3.0]
721    /// ```
722    pub fn log2(&self) -> Result<Array> {
723        let stream = Stream::thread_local_or_default();
724        Array::try_from_op(|res| unsafe {
725            mlx_sys::mlx_log2(res, self.as_ptr(), stream.as_ref().as_ptr())
726        })
727    }
728
729    /// Compatibility shim for [`log2`].
730    #[deprecated(
731        since = "0.26.0",
732        note = "use `with_stream` or `with_device` around `log2`"
733    )]
734    pub fn log2_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
735        crate::with_stream(stream.as_ref(), || self.log2())
736    }
737
738    /// Element-wise base-10 logarithm.
739    ///
740    /// # Example
741    ///
742    /// ```rust
743    /// use mlx_rs::Array;
744    /// let a = Array::from_slice(&[1.0, 10.0, 100.0], &[3]);
745    /// let mut b = a.log10().unwrap();
746    ///
747    /// let b_data: &[f32] = b.as_slice();
748    /// // b_data == [0.0, 1.0, 2.0]
749    /// ```
750    pub fn log10(&self) -> Result<Array> {
751        let stream = Stream::thread_local_or_default();
752        Array::try_from_op(|res| unsafe {
753            mlx_sys::mlx_log10(res, self.as_ptr(), stream.as_ref().as_ptr())
754        })
755    }
756
757    /// Compatibility shim for [`log10`].
758    #[deprecated(
759        since = "0.26.0",
760        note = "use `with_stream` or `with_device` around `log10`"
761    )]
762    pub fn log10_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
763        crate::with_stream(stream.as_ref(), || self.log10())
764    }
765
766    /// Element-wise natural log of one plus the array.
767    ///
768    /// # Example
769    ///
770    /// ```rust
771    /// use mlx_rs::Array;
772    /// let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
773    /// let mut b = a.log1p().unwrap();
774    ///
775    /// let b_data: &[f32] = b.as_slice();
776    /// // b_data == [0.6931472, 1.0986123, 1.3862944]
777    /// ```
778    pub fn log1p(&self) -> Result<Array> {
779        let stream = Stream::thread_local_or_default();
780        Array::try_from_op(|res| unsafe {
781            mlx_sys::mlx_log1p(res, self.as_ptr(), stream.as_ref().as_ptr())
782        })
783    }
784
785    /// Compatibility shim for [`log1p`].
786    #[deprecated(
787        since = "0.26.0",
788        note = "use `with_stream` or `with_device` around `log1p`"
789    )]
790    pub fn log1p_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
791        crate::with_stream(stream.as_ref(), || self.log1p())
792    }
793
794    /// Matrix multiplication returning an error if inputs are not valid.
795    ///
796    /// Perform the (possibly batched) matrix multiplication of two arrays. This function supports
797    /// broadcasting for arrays with more than two dimensions.
798    ///
799    /// - If the first array is 1-D then a 1 is prepended to its shape to make it
800    ///   a matrix. Similarly, if the second array is 1-D then a 1 is appended to its
801    ///   shape to make it a matrix. In either case the singleton dimension is removed
802    ///   from the result.
803    /// - A batched matrix multiplication is performed if the arrays have more than
804    ///   2 dimensions.  The matrix dimensions for the matrix product are the last
805    ///   two dimensions of each input.
806    /// - All but the last two dimensions of each input are broadcast with one another using
807    ///   standard [broadcasting](https://swiftpackageindex.com/ml-explore/mlx-swift/main/documentation/mlx/broadcasting).
808    ///
809    /// # Params
810    ///
811    /// - other: array to multiply
812    ///
813    /// # Example
814    ///
815    /// ```rust
816    /// use mlx_rs::Array;
817    /// let a = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
818    /// let b = Array::from_slice(&[-5.0, 37.5, 4., 7., 1., 0.], &[2, 3]);
819    ///
820    /// // produces a [2, 3] result
821    /// let mut c = a.matmul(&b);
822    /// ```
823    pub fn matmul(&self, other: impl AsRef<Array>) -> Result<Array> {
824        let stream = Stream::thread_local_or_default();
825        Array::try_from_op(|res| unsafe {
826            mlx_sys::mlx_matmul(
827                res,
828                self.as_ptr(),
829                other.as_ref().as_ptr(),
830                stream.as_ref().as_ptr(),
831            )
832        })
833    }
834
835    /// Compatibility shim for [`matmul`].
836    #[deprecated(
837        since = "0.26.0",
838        note = "use `with_stream` or `with_device` around `matmul`"
839    )]
840    pub fn matmul_device(
841        &self,
842        other: impl AsRef<Array>,
843        stream: impl AsRef<Stream>,
844    ) -> Result<Array> {
845        crate::with_stream(stream.as_ref(), || self.matmul(other))
846    }
847
848    /// Element-wise reciprocal.
849    ///
850    /// # Example
851    ///
852    /// ```rust
853    /// use mlx_rs::Array;
854    /// let a = Array::from_slice(&[1.0, 2.0, 4.0], &[3]);
855    /// let mut b = a.reciprocal().unwrap();
856    ///
857    /// let b_data: &[f32] = b.as_slice();
858    /// // b_data == [1.0, 0.5, 0.25]
859    /// ```
860    pub fn reciprocal(&self) -> Result<Array> {
861        let stream = Stream::thread_local_or_default();
862        Array::try_from_op(|res| unsafe {
863            mlx_sys::mlx_reciprocal(res, self.as_ptr(), stream.as_ref().as_ptr())
864        })
865    }
866
867    /// Compatibility shim for [`reciprocal`].
868    #[deprecated(
869        since = "0.26.0",
870        note = "use `with_stream` or `with_device` around `reciprocal`"
871    )]
872    pub fn reciprocal_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
873        crate::with_stream(stream.as_ref(), || self.reciprocal())
874    }
875
876    /// Round to the given number of decimals.
877    ///
878    /// # Params
879    ///
880    /// - decimals: number of decimals to round to - default is 0 if not provided
881    pub fn round(&self, decimals: impl Into<Option<i32>>) -> Result<Array> {
882        let stream = Stream::thread_local_or_default();
883        Array::try_from_op(|res| unsafe {
884            mlx_sys::mlx_round(
885                res,
886                self.as_ptr(),
887                decimals.into().unwrap_or(0),
888                stream.as_ref().as_ptr(),
889            )
890        })
891    }
892
893    /// Compatibility shim for [`round`].
894    #[deprecated(
895        since = "0.26.0",
896        note = "use `with_stream` or `with_device` around `round`"
897    )]
898    pub fn round_device(
899        &self,
900        decimals: impl Into<Option<i32>>,
901        stream: impl AsRef<Stream>,
902    ) -> Result<Array> {
903        crate::with_stream(stream.as_ref(), || self.round(decimals))
904    }
905
906    /// Element-wise reciprocal and square root.
907    pub fn rsqrt(&self) -> Result<Array> {
908        let stream = Stream::thread_local_or_default();
909        Array::try_from_op(|res| unsafe {
910            mlx_sys::mlx_rsqrt(res, self.as_ptr(), stream.as_ref().as_ptr())
911        })
912    }
913
914    /// Compatibility shim for [`rsqrt`].
915    #[deprecated(
916        since = "0.26.0",
917        note = "use `with_stream` or `with_device` around `rsqrt`"
918    )]
919    pub fn rsqrt_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
920        crate::with_stream(stream.as_ref(), || self.rsqrt())
921    }
922
923    /// Element-wise sine.
924    pub fn sin(&self) -> Result<Array> {
925        let stream = Stream::thread_local_or_default();
926        Array::try_from_op(|res| unsafe {
927            mlx_sys::mlx_sin(res, self.as_ptr(), stream.as_ref().as_ptr())
928        })
929    }
930
931    /// Compatibility shim for [`sin`].
932    #[deprecated(
933        since = "0.26.0",
934        note = "use `with_stream` or `with_device` around `sin`"
935    )]
936    pub fn sin_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
937        crate::with_stream(stream.as_ref(), || self.sin())
938    }
939
940    /// Element-wise square.
941    pub fn square(&self) -> Result<Array> {
942        let stream = Stream::thread_local_or_default();
943        Array::try_from_op(|res| unsafe {
944            mlx_sys::mlx_square(res, self.as_ptr(), stream.as_ref().as_ptr())
945        })
946    }
947
948    /// Compatibility shim for [`square`].
949    #[deprecated(
950        since = "0.26.0",
951        note = "use `with_stream` or `with_device` around `square`"
952    )]
953    pub fn square_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
954        crate::with_stream(stream.as_ref(), || self.square())
955    }
956
957    /// Element-wise real part from a complex array.
958    pub fn real(&self) -> Result<Array> {
959        let stream = Stream::thread_local_or_default();
960        Array::try_from_op(|res| unsafe {
961            mlx_sys::mlx_real(res, self.as_ptr(), stream.as_ref().as_ptr())
962        })
963    }
964
965    /// Compatibility shim for [`real`].
966    #[deprecated(
967        since = "0.26.0",
968        note = "use `with_stream` or `with_device` around `real`"
969    )]
970    pub fn real_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
971        crate::with_stream(stream.as_ref(), || self.real())
972    }
973
974    /// Element-wise imag part from a complex array.
975    pub fn imag(&self) -> Result<Array> {
976        let stream = Stream::thread_local_or_default();
977        Array::try_from_op(|res| unsafe {
978            mlx_sys::mlx_imag(res, self.as_ptr(), stream.as_ref().as_ptr())
979        })
980    }
981
982    /// Compatibility shim for [`imag`].
983    #[deprecated(
984        since = "0.26.0",
985        note = "use `with_stream` or `with_device` around `imag`"
986    )]
987    pub fn imag_device(&self, stream: impl AsRef<Stream>) -> Result<Array> {
988        crate::with_stream(stream.as_ref(), || self.imag())
989    }
990}
991
992/// Element-wise absolute value.
993///
994/// # Example
995///
996/// ```rust
997/// use mlx_rs::{Array, ops};
998///
999/// let array = Array::from_slice(&[1i32, 2, -3, -4, -5], &[5]);
1000/// let result = ops::abs(&array).unwrap();
1001/// ```
1002pub fn abs(a: impl AsRef<Array>) -> Result<Array> {
1003    a.as_ref().abs()
1004}
1005
1006/// Compatibility shim for [`abs`].
1007#[generate_macro(customize(forwarding_shim = true))]
1008#[deprecated(
1009    since = "0.26.0",
1010    note = "use `with_stream` or `with_device` around `abs`"
1011)]
1012pub fn abs_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1013    crate::with_stream(stream.as_ref(), || abs(a))
1014}
1015
1016/// Element-wise inverse cosine.
1017pub fn acos(a: impl AsRef<Array>) -> Result<Array> {
1018    let stream = Stream::thread_local_or_default();
1019    Array::try_from_op(|res| unsafe {
1020        mlx_sys::mlx_arccos(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1021    })
1022}
1023
1024/// Compatibility shim for [`acos`].
1025#[generate_macro(customize(forwarding_shim = true))]
1026#[deprecated(
1027    since = "0.26.0",
1028    note = "use `with_stream` or `with_device` around `acos`"
1029)]
1030pub fn acos_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1031    crate::with_stream(stream.as_ref(), || acos(a))
1032}
1033
1034/// Element-wise inverse hyperbolic cosine.
1035pub fn acosh(a: impl AsRef<Array>) -> Result<Array> {
1036    let stream = Stream::thread_local_or_default();
1037    Array::try_from_op(|res| unsafe {
1038        mlx_sys::mlx_arccosh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1039    })
1040}
1041
1042/// Compatibility shim for [`acosh`].
1043#[generate_macro(customize(forwarding_shim = true))]
1044#[deprecated(
1045    since = "0.26.0",
1046    note = "use `with_stream` or `with_device` around `acosh`"
1047)]
1048pub fn acosh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1049    crate::with_stream(stream.as_ref(), || acosh(a))
1050}
1051
1052/// Compute a vector dot product along `axis`.
1053///
1054/// The first operand is conjugated, the remaining dimensions are broadcast, and the selected
1055/// axis is reduced. Unequal-rank axis normalization is not part of this API's contract.
1056///
1057/// ```rust
1058/// use mlx_rs::{array, ops::vecdot};
1059///
1060/// let result = vecdot(array!([1.0, 2.0]), array!([3.0, 4.0]), -1).unwrap();
1061/// assert!(result.shape().is_empty());
1062/// ```
1063pub fn vecdot(lhs: impl AsRef<Array>, rhs: impl AsRef<Array>, axis: i32) -> Result<Array> {
1064    let stream = Stream::thread_local_or_default();
1065    Array::try_from_op(|res| unsafe {
1066        mlx_sys::mlx_vecdot(
1067            res,
1068            lhs.as_ref().as_ptr(),
1069            rhs.as_ref().as_ptr(),
1070            axis,
1071            stream.as_ref().as_ptr(),
1072        )
1073    })
1074}
1075
1076/// See [`Array::add`].
1077pub fn add(lhs: impl AsRef<Array>, rhs: impl AsRef<Array>) -> Result<Array> {
1078    lhs.as_ref().add(rhs)
1079}
1080
1081/// Compatibility shim for [`add`].
1082#[generate_macro(customize(forwarding_shim = true))]
1083#[deprecated(
1084    since = "0.26.0",
1085    note = "use `with_stream` or `with_device` around `add`"
1086)]
1087pub fn add_device(
1088    lhs: impl AsRef<Array>,
1089    rhs: impl AsRef<Array>,
1090    #[optional] stream: impl AsRef<Stream>,
1091) -> Result<Array> {
1092    crate::with_stream(stream.as_ref(), || add(lhs, rhs))
1093}
1094
1095/// Element-wise inverse sine.
1096pub fn asin(a: impl AsRef<Array>) -> Result<Array> {
1097    let stream = Stream::thread_local_or_default();
1098    Array::try_from_op(|res| unsafe {
1099        mlx_sys::mlx_arcsin(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1100    })
1101}
1102
1103/// Compatibility shim for [`asin`].
1104#[generate_macro(customize(forwarding_shim = true))]
1105#[deprecated(
1106    since = "0.26.0",
1107    note = "use `with_stream` or `with_device` around `asin`"
1108)]
1109pub fn asin_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1110    crate::with_stream(stream.as_ref(), || asin(a))
1111}
1112
1113/// Element-wise inverse hyperbolic sine.
1114pub fn asinh(a: impl AsRef<Array>) -> Result<Array> {
1115    let stream = Stream::thread_local_or_default();
1116    Array::try_from_op(|res| unsafe {
1117        mlx_sys::mlx_arcsinh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1118    })
1119}
1120
1121/// Compatibility shim for [`asinh`].
1122#[generate_macro(customize(forwarding_shim = true))]
1123#[deprecated(
1124    since = "0.26.0",
1125    note = "use `with_stream` or `with_device` around `asinh`"
1126)]
1127pub fn asinh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1128    crate::with_stream(stream.as_ref(), || asinh(a))
1129}
1130
1131/// Element-wise inverse tangent.
1132pub fn atan(a: impl AsRef<Array>) -> Result<Array> {
1133    let stream = Stream::thread_local_or_default();
1134    Array::try_from_op(|res| unsafe {
1135        mlx_sys::mlx_arctan(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1136    })
1137}
1138
1139/// Compatibility shim for [`atan`].
1140#[generate_macro(customize(forwarding_shim = true))]
1141#[deprecated(
1142    since = "0.26.0",
1143    note = "use `with_stream` or `with_device` around `atan`"
1144)]
1145pub fn atan_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1146    crate::with_stream(stream.as_ref(), || atan(a))
1147}
1148
1149/// Element-wise inverse tangent of b/a choosing the quadrant correctly.
1150pub fn atan2(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1151    let stream = Stream::thread_local_or_default();
1152    let a = a.as_ref();
1153    let b = b.as_ref();
1154
1155    Array::try_from_op(|res| unsafe {
1156        mlx_sys::mlx_arctan2(res, a.as_ptr(), b.as_ptr(), stream.as_ref().as_ptr())
1157    })
1158}
1159
1160/// Compatibility shim for [`atan2`].
1161#[generate_macro(customize(forwarding_shim = true))]
1162#[deprecated(
1163    since = "0.26.0",
1164    note = "use `with_stream` or `with_device` around `atan2`"
1165)]
1166pub fn atan2_device(
1167    a: impl AsRef<Array>,
1168    b: impl AsRef<Array>,
1169    #[optional] stream: impl AsRef<Stream>,
1170) -> Result<Array> {
1171    crate::with_stream(stream.as_ref(), || atan2(a, b))
1172}
1173
1174/// Element-wise inverse hyperbolic tangent.
1175pub fn atanh(a: impl AsRef<Array>) -> Result<Array> {
1176    let stream = Stream::thread_local_or_default();
1177    Array::try_from_op(|res| unsafe {
1178        mlx_sys::mlx_arctanh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1179    })
1180}
1181
1182/// Compatibility shim for [`atanh`].
1183#[generate_macro(customize(forwarding_shim = true))]
1184#[deprecated(
1185    since = "0.26.0",
1186    note = "use `with_stream` or `with_device` around `atanh`"
1187)]
1188pub fn atanh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1189    crate::with_stream(stream.as_ref(), || atanh(a))
1190}
1191
1192/// Element-wise ceiling.
1193pub fn ceil(a: impl AsRef<Array>) -> Result<Array> {
1194    let stream = Stream::thread_local_or_default();
1195    Array::try_from_op(|res| unsafe {
1196        mlx_sys::mlx_ceil(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1197    })
1198}
1199
1200/// Compatibility shim for [`ceil`].
1201#[generate_macro(customize(forwarding_shim = true))]
1202#[deprecated(
1203    since = "0.26.0",
1204    note = "use `with_stream` or `with_device` around `ceil`"
1205)]
1206pub fn ceil_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1207    crate::with_stream(stream.as_ref(), || ceil(a))
1208}
1209
1210/// A custom trait for the bound of the clip operation.
1211///
1212/// This trait is only implemented for tuples of the form `(Min, Max)`, `(Min, ())`, and `((),
1213/// Max)`. The `Min` and `Max` types must implement the `ScalarOrArray` trait.
1214pub trait ClipBound<'min, 'max>: Sealed {
1215    /// Convert the bound into a tuple of optional minimum and maximum values.
1216    fn into_min_max(
1217        self,
1218    ) -> (
1219        Option<impl ScalarOrArray<'min>>,
1220        Option<impl ScalarOrArray<'max>>,
1221    );
1222}
1223
1224impl<'min, Min> ClipBound<'min, 'min> for (Min, ())
1225where
1226    Min: ScalarOrArray<'min> + Sealed,
1227{
1228    fn into_min_max(
1229        self,
1230    ) -> (
1231        Option<impl ScalarOrArray<'min>>,
1232        Option<impl ScalarOrArray<'min>>,
1233    ) {
1234        (Some(self.0), Option::<Min>::None)
1235    }
1236}
1237
1238impl<'max, Max> ClipBound<'max, 'max> for ((), Max)
1239where
1240    Max: ScalarOrArray<'max> + Sealed,
1241{
1242    fn into_min_max(
1243        self,
1244    ) -> (
1245        Option<impl ScalarOrArray<'max>>,
1246        Option<impl ScalarOrArray<'max>>,
1247    ) {
1248        (Option::<Max>::None, Some(self.1))
1249    }
1250}
1251
1252impl<'min, 'max, Min, Max> ClipBound<'min, 'max> for (Min, Max)
1253where
1254    Min: ScalarOrArray<'min> + Sealed,
1255    Max: ScalarOrArray<'max> + Sealed,
1256{
1257    fn into_min_max(
1258        self,
1259    ) -> (
1260        Option<impl ScalarOrArray<'min>>,
1261        Option<impl ScalarOrArray<'max>>,
1262    ) {
1263        (Some(self.0), Some(self.1))
1264    }
1265}
1266
1267/// Clip the values of the array between the given minimum and maximum.
1268///
1269/// If either `a_min` or `a_max` are None, then corresponding edge is ignored. At least one of
1270/// `a_min` and `a_max` cannot be `None`. The input `a` and the limits must broadcast with one
1271/// another.
1272///
1273/// # Params
1274///
1275/// - `a`: Input array.
1276/// - `bound`: minimum and/or maximum values to clip the array to.
1277///
1278/// # Example
1279///
1280/// ```rust
1281/// use mlx_rs::{Array, ops::clip, array};
1282///
1283/// let a = array!([1.0, 4.0, 3.0, 8.0, 5.0]);
1284/// let expected = array!([2.0, 4.0, 3.0, 6.0, 5.0]);
1285/// let clipped = clip(&a, (2.0, 6.0)).unwrap();
1286/// assert!(clipped.eq_exact(&expected).unwrap());
1287/// ```
1288pub fn clip<'min, 'max>(a: impl AsRef<Array>, bound: impl ClipBound<'min, 'max>) -> Result<Array> {
1289    let stream = Stream::thread_local_or_default();
1290    let (a_min, a_max) = bound.into_min_max();
1291
1292    // This is needed to keep the lifetime of the min/max arrays in scope.
1293    let a_min = a_min.map(|min| min.into_owned_or_ref_array());
1294    let a_max = a_max.map(|max| max.into_owned_or_ref_array());
1295
1296    unsafe {
1297        let min_ptr = match &a_min {
1298            Some(a_min) => a_min.as_ref().as_ptr(),
1299            None => mlx_sys::mlx_array_new(),
1300        };
1301        let max_ptr = match &a_max {
1302            Some(a_max) => a_max.as_ref().as_ptr(),
1303            None => mlx_sys::mlx_array_new(),
1304        };
1305
1306        Array::try_from_op(|res| {
1307            mlx_sys::mlx_clip(
1308                res,
1309                a.as_ref().as_ptr(),
1310                min_ptr,
1311                max_ptr,
1312                stream.as_ref().as_ptr(),
1313            )
1314        })
1315    }
1316}
1317
1318/// Compatibility shim for [`clip`].
1319#[generate_macro(customize(forwarding_shim = true))]
1320#[deprecated(
1321    since = "0.26.0",
1322    note = "use `with_stream` or `with_device` around `clip`"
1323)]
1324pub fn clip_device<'min, 'max>(
1325    a: impl AsRef<Array>,
1326    bound: impl ClipBound<'min, 'max>,
1327    #[optional] stream: impl AsRef<Stream>,
1328) -> Result<Array> {
1329    crate::with_stream(stream.as_ref(), || clip(a, bound))
1330}
1331
1332/// Element-wise cosine.
1333pub fn cos(a: impl AsRef<Array>) -> Result<Array> {
1334    a.as_ref().cos()
1335}
1336
1337/// Compatibility shim for [`cos`].
1338#[generate_macro(customize(forwarding_shim = true))]
1339#[deprecated(
1340    since = "0.26.0",
1341    note = "use `with_stream` or `with_device` around `cos`"
1342)]
1343pub fn cos_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1344    crate::with_stream(stream.as_ref(), || cos(a))
1345}
1346
1347/// Element-wise hyperbolic cosine.
1348pub fn cosh(a: impl AsRef<Array>) -> Result<Array> {
1349    let stream = Stream::thread_local_or_default();
1350    Array::try_from_op(|res| unsafe {
1351        mlx_sys::mlx_cosh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1352    })
1353}
1354
1355/// Compatibility shim for [`cosh`].
1356#[generate_macro(customize(forwarding_shim = true))]
1357#[deprecated(
1358    since = "0.26.0",
1359    note = "use `with_stream` or `with_device` around `cosh`"
1360)]
1361pub fn cosh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1362    crate::with_stream(stream.as_ref(), || cosh(a))
1363}
1364
1365/// Convert angles from radians to degrees.
1366pub fn degrees(a: impl AsRef<Array>) -> Result<Array> {
1367    let stream = Stream::thread_local_or_default();
1368    Array::try_from_op(|res| unsafe {
1369        mlx_sys::mlx_degrees(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1370    })
1371}
1372
1373/// Compatibility shim for [`degrees`].
1374#[generate_macro(customize(forwarding_shim = true))]
1375#[deprecated(
1376    since = "0.26.0",
1377    note = "use `with_stream` or `with_device` around `degrees`"
1378)]
1379pub fn degrees_device(
1380    a: impl AsRef<Array>,
1381    #[optional] stream: impl AsRef<Stream>,
1382) -> Result<Array> {
1383    crate::with_stream(stream.as_ref(), || degrees(a))
1384}
1385
1386/// See [`Array::divide`].
1387pub fn divide(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1388    a.as_ref().divide(b)
1389}
1390
1391/// Compatibility shim for [`divide`].
1392#[generate_macro(customize(forwarding_shim = true))]
1393#[deprecated(
1394    since = "0.26.0",
1395    note = "use `with_stream` or `with_device` around `divide`"
1396)]
1397pub fn divide_device(
1398    a: impl AsRef<Array>,
1399    b: impl AsRef<Array>,
1400    #[optional] stream: impl AsRef<Stream>,
1401) -> Result<Array> {
1402    crate::with_stream(stream.as_ref(), || divide(a, b))
1403}
1404
1405/// Element-wise quotient and remainder.
1406///
1407/// The fuction `divmod(a, b)` is equivalent to but faster than `(a // b, a % b)`. The function uses
1408/// numpy-style broadcasting semantics. Either or both input arrays can also be scalars.
1409///
1410/// Returns Ok((quotient, remainder)) if the operation was successful.
1411pub fn divmod(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<(Array, Array)> {
1412    let stream = Stream::thread_local_or_default();
1413    let a_ptr = a.as_ref().as_ptr();
1414    let b_ptr = b.as_ref().as_ptr();
1415
1416    let vec = VectorArray::try_from_op(|res| unsafe {
1417        mlx_sys::mlx_divmod(res, a_ptr, b_ptr, stream.as_ref().as_ptr())
1418    })?;
1419
1420    let vals: SmallVec<[_; 2]> = vec.try_into_values()?;
1421    let mut iter = vals.into_iter();
1422    let quotient = iter.next().unwrap();
1423    let remainder = iter.next().unwrap();
1424
1425    Ok((quotient, remainder))
1426}
1427
1428/// Compatibility shim for [`divmod`].
1429#[generate_macro(customize(forwarding_shim = true))]
1430#[deprecated(
1431    since = "0.26.0",
1432    note = "use `with_stream` or `with_device` around `divmod`"
1433)]
1434pub fn divmod_device(
1435    a: impl AsRef<Array>,
1436    b: impl AsRef<Array>,
1437    #[optional] stream: impl AsRef<Stream>,
1438) -> Result<(Array, Array)> {
1439    crate::with_stream(stream.as_ref(), || divmod(a, b))
1440}
1441
1442/// Element-wise error function.
1443pub fn erf(a: impl AsRef<Array>) -> Result<Array> {
1444    let stream = Stream::thread_local_or_default();
1445    Array::try_from_op(|res| unsafe {
1446        mlx_sys::mlx_erf(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1447    })
1448}
1449
1450/// Compatibility shim for [`erf`].
1451#[generate_macro(customize(forwarding_shim = true))]
1452#[deprecated(
1453    since = "0.26.0",
1454    note = "use `with_stream` or `with_device` around `erf`"
1455)]
1456pub fn erf_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1457    crate::with_stream(stream.as_ref(), || erf(a))
1458}
1459
1460/// Element-wise inverse error function.
1461pub fn erfinv(a: impl AsRef<Array>) -> Result<Array> {
1462    let stream = Stream::thread_local_or_default();
1463    Array::try_from_op(|res| unsafe {
1464        mlx_sys::mlx_erfinv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1465    })
1466}
1467
1468/// Compatibility shim for [`erfinv`].
1469#[generate_macro(customize(forwarding_shim = true))]
1470#[deprecated(
1471    since = "0.26.0",
1472    note = "use `with_stream` or `with_device` around `erfinv`"
1473)]
1474pub fn erfinv_device(
1475    a: impl AsRef<Array>,
1476    #[optional] stream: impl AsRef<Stream>,
1477) -> Result<Array> {
1478    crate::with_stream(stream.as_ref(), || erfinv(a))
1479}
1480
1481/// See [`Array::exp`].
1482pub fn exp(a: impl AsRef<Array>) -> Result<Array> {
1483    a.as_ref().exp()
1484}
1485
1486/// Compatibility shim for [`exp`].
1487#[generate_macro(customize(forwarding_shim = true))]
1488#[deprecated(
1489    since = "0.26.0",
1490    note = "use `with_stream` or `with_device` around `exp`"
1491)]
1492pub fn exp_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1493    crate::with_stream(stream.as_ref(), || exp(a))
1494}
1495
1496/// Element-wise exponential minus 1.
1497pub fn expm1(a: impl AsRef<Array>) -> Result<Array> {
1498    let stream = Stream::thread_local_or_default();
1499    Array::try_from_op(|res| unsafe {
1500        mlx_sys::mlx_expm1(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1501    })
1502}
1503
1504/// Compatibility shim for [`expm1`].
1505#[generate_macro(customize(forwarding_shim = true))]
1506#[deprecated(
1507    since = "0.26.0",
1508    note = "use `with_stream` or `with_device` around `expm1`"
1509)]
1510pub fn expm1_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1511    crate::with_stream(stream.as_ref(), || expm1(a))
1512}
1513
1514/// See [`Array::floor`].
1515pub fn floor(a: impl AsRef<Array>) -> Result<Array> {
1516    a.as_ref().floor()
1517}
1518
1519/// Compatibility shim for [`floor`].
1520#[generate_macro(customize(forwarding_shim = true))]
1521#[deprecated(
1522    since = "0.26.0",
1523    note = "use `with_stream` or `with_device` around `floor`"
1524)]
1525pub fn floor_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1526    crate::with_stream(stream.as_ref(), || floor(a))
1527}
1528
1529/// See [`Array::floor_divide`].
1530pub fn floor_divide(a: impl AsRef<Array>, other: impl AsRef<Array>) -> Result<Array> {
1531    a.as_ref().floor_divide(other)
1532}
1533
1534/// Compatibility shim for [`floor_divide`].
1535#[generate_macro(customize(forwarding_shim = true))]
1536#[deprecated(
1537    since = "0.26.0",
1538    note = "use `with_stream` or `with_device` around `floor_divide`"
1539)]
1540pub fn floor_divide_device(
1541    a: impl AsRef<Array>,
1542    other: impl AsRef<Array>,
1543    #[optional] stream: impl AsRef<Stream>,
1544) -> Result<Array> {
1545    crate::with_stream(stream.as_ref(), || floor_divide(a, other))
1546}
1547
1548/// See [`Array::log`].
1549pub fn log(a: impl AsRef<Array>) -> Result<Array> {
1550    a.as_ref().log()
1551}
1552
1553/// Compatibility shim for [`log`].
1554#[generate_macro(customize(forwarding_shim = true))]
1555#[deprecated(
1556    since = "0.26.0",
1557    note = "use `with_stream` or `with_device` around `log`"
1558)]
1559pub fn log_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1560    crate::with_stream(stream.as_ref(), || log(a))
1561}
1562
1563/// See [`Array::log10`].
1564pub fn log10(a: impl AsRef<Array>) -> Result<Array> {
1565    a.as_ref().log10()
1566}
1567
1568/// Compatibility shim for [`log10`].
1569#[generate_macro(customize(forwarding_shim = true))]
1570#[deprecated(
1571    since = "0.26.0",
1572    note = "use `with_stream` or `with_device` around `log10`"
1573)]
1574pub fn log10_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1575    crate::with_stream(stream.as_ref(), || log10(a))
1576}
1577
1578/// See [`Array::log1p`].
1579pub fn log1p(a: impl AsRef<Array>) -> Result<Array> {
1580    a.as_ref().log1p()
1581}
1582
1583/// Compatibility shim for [`log1p`].
1584#[generate_macro(customize(forwarding_shim = true))]
1585#[deprecated(
1586    since = "0.26.0",
1587    note = "use `with_stream` or `with_device` around `log1p`"
1588)]
1589pub fn log1p_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1590    crate::with_stream(stream.as_ref(), || log1p(a))
1591}
1592
1593/// See [`Array::log2`].
1594pub fn log2(a: impl AsRef<Array>) -> Result<Array> {
1595    a.as_ref().log2()
1596}
1597
1598/// Compatibility shim for [`log2`].
1599#[generate_macro(customize(forwarding_shim = true))]
1600#[deprecated(
1601    since = "0.26.0",
1602    note = "use `with_stream` or `with_device` around `log2`"
1603)]
1604pub fn log2_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1605    crate::with_stream(stream.as_ref(), || log2(a))
1606}
1607
1608/// Element-wise log-add-exp.
1609///
1610/// This is a numerically stable log-add-exp of two arrays with numpy-style broadcasting semantics.
1611/// Either or both input arrays can also be scalars.
1612///
1613/// The computation is is a numerically stable version of `log(exp(a) + exp(b))`.
1614pub fn logaddexp(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1615    let stream = Stream::thread_local_or_default();
1616    let a_ptr = a.as_ref().as_ptr();
1617    let b_ptr = b.as_ref().as_ptr();
1618
1619    Array::try_from_op(|res| unsafe {
1620        mlx_sys::mlx_logaddexp(res, a_ptr, b_ptr, stream.as_ref().as_ptr())
1621    })
1622}
1623
1624/// Compatibility shim for [`logaddexp`].
1625#[generate_macro(customize(forwarding_shim = true))]
1626#[deprecated(
1627    since = "0.26.0",
1628    note = "use `with_stream` or `with_device` around `logaddexp`"
1629)]
1630pub fn logaddexp_device(
1631    a: impl AsRef<Array>,
1632    b: impl AsRef<Array>,
1633    #[optional] stream: impl AsRef<Stream>,
1634) -> Result<Array> {
1635    crate::with_stream(stream.as_ref(), || logaddexp(a, b))
1636}
1637
1638/// See [`Array::matmul`].
1639pub fn matmul(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1640    a.as_ref().matmul(b)
1641}
1642
1643/// Compatibility shim for [`matmul`].
1644#[generate_macro(customize(forwarding_shim = true))]
1645#[deprecated(
1646    since = "0.26.0",
1647    note = "use `with_stream` or `with_device` around `matmul`"
1648)]
1649pub fn matmul_device(
1650    a: impl AsRef<Array>,
1651    b: impl AsRef<Array>,
1652    #[optional] stream: impl AsRef<Stream>,
1653) -> Result<Array> {
1654    crate::with_stream(stream.as_ref(), || matmul(a, b))
1655}
1656
1657/// Perform a segmented matrix multiplication.
1658///
1659/// This computes multiple matrix multiplications where each segment of the reduction
1660/// dimension is multiplied independently. This is useful for operations like mixture
1661/// of experts or multi-head attention where different segments use different weights.
1662///
1663/// # Params
1664///
1665/// - `a`: Input array with shape `(M, K)`
1666/// - `b`: Input array with shape `(K, N)`
1667/// - `segments`: Array of segment boundaries with shape `(num_segments, 2)`.
1668///   Each row contains `[start, end)` indices along the K dimension.
1669///
1670/// # Returns
1671///
1672/// Array with shape `(num_segments, M, N)` where each segment contains the matrix
1673/// multiplication for that segment of the K dimension.
1674///
1675/// # Example
1676///
1677/// ```rust,ignore
1678/// use mlx_rs::{Array, ops::segmented_mm};
1679///
1680/// let a = Array::ones::<f32>(&[10, 100]).unwrap();
1681/// let b = Array::ones::<f32>(&[100, 10]).unwrap();
1682/// let segments = Array::from_slice(&[0u32, 50, 50, 100], &[2, 2]);
1683/// let result = segmented_mm(&a, &b, &segments, None).unwrap();
1684/// // result has shape [2, 10, 10]
1685/// ```
1686pub fn segmented_mm(
1687    a: impl AsRef<Array>,
1688    b: impl AsRef<Array>,
1689    segments: impl AsRef<Array>,
1690) -> Result<Array> {
1691    let stream = Stream::thread_local_or_default();
1692    Array::try_from_op(|res| unsafe {
1693        mlx_sys::mlx_segmented_mm(
1694            res,
1695            a.as_ref().as_ptr(),
1696            b.as_ref().as_ptr(),
1697            segments.as_ref().as_ptr(),
1698            stream.as_ref().as_ptr(),
1699        )
1700    })
1701}
1702
1703/// Compatibility shim for [`segmented_mm`].
1704#[generate_macro(customize(forwarding_shim = true))]
1705#[deprecated(
1706    since = "0.26.0",
1707    note = "use `with_stream` or `with_device` around `segmented_mm`"
1708)]
1709pub fn segmented_mm_device(
1710    a: impl AsRef<Array>,
1711    b: impl AsRef<Array>,
1712    segments: impl AsRef<Array>,
1713    #[optional] stream: impl AsRef<Stream>,
1714) -> Result<Array> {
1715    crate::with_stream(stream.as_ref(), || segmented_mm(a, b, segments))
1716}
1717
1718/// Element-wise maximum.
1719///
1720/// Take the element-wise max of two arrays with numpy-style broadcasting semantics. Either or both
1721/// input arrays can also be scalars.
1722pub fn maximum(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1723    let stream = Stream::thread_local_or_default();
1724    let a_ptr = a.as_ref().as_ptr();
1725    let b_ptr = b.as_ref().as_ptr();
1726
1727    Array::try_from_op(|res| unsafe {
1728        mlx_sys::mlx_maximum(res, a_ptr, b_ptr, stream.as_ref().as_ptr())
1729    })
1730}
1731
1732/// Compatibility shim for [`maximum`].
1733#[generate_macro(customize(forwarding_shim = true))]
1734#[deprecated(
1735    since = "0.26.0",
1736    note = "use `with_stream` or `with_device` around `maximum`"
1737)]
1738pub fn maximum_device(
1739    a: impl AsRef<Array>,
1740    b: impl AsRef<Array>,
1741    #[optional] stream: impl AsRef<Stream>,
1742) -> Result<Array> {
1743    crate::with_stream(stream.as_ref(), || maximum(a, b))
1744}
1745
1746/// Element-wise minimum.
1747///
1748/// Take the element-wise min of two arrays with numpy-style broadcasting semantics. Either or both
1749/// input arrays can also be scalars.
1750pub fn minimum(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1751    let stream = Stream::thread_local_or_default();
1752    let a_ptr = a.as_ref().as_ptr();
1753    let b_ptr = b.as_ref().as_ptr();
1754
1755    Array::try_from_op(|res| unsafe {
1756        mlx_sys::mlx_minimum(res, a_ptr, b_ptr, stream.as_ref().as_ptr())
1757    })
1758}
1759
1760/// Compatibility shim for [`minimum`].
1761#[generate_macro(customize(forwarding_shim = true))]
1762#[deprecated(
1763    since = "0.26.0",
1764    note = "use `with_stream` or `with_device` around `minimum`"
1765)]
1766pub fn minimum_device(
1767    a: impl AsRef<Array>,
1768    b: impl AsRef<Array>,
1769    #[optional] stream: impl AsRef<Stream>,
1770) -> Result<Array> {
1771    crate::with_stream(stream.as_ref(), || minimum(a, b))
1772}
1773
1774/// See [`Array::multiply`].
1775pub fn multiply(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1776    a.as_ref().multiply(b)
1777}
1778
1779/// Compatibility shim for [`multiply`].
1780#[generate_macro(customize(forwarding_shim = true))]
1781#[deprecated(
1782    since = "0.26.0",
1783    note = "use `with_stream` or `with_device` around `multiply`"
1784)]
1785pub fn multiply_device(
1786    a: impl AsRef<Array>,
1787    b: impl AsRef<Array>,
1788    #[optional] stream: impl AsRef<Stream>,
1789) -> Result<Array> {
1790    crate::with_stream(stream.as_ref(), || multiply(a, b))
1791}
1792
1793/// See [`Array::negative`].
1794pub fn negative(a: impl AsRef<Array>) -> Result<Array> {
1795    a.as_ref().negative()
1796}
1797
1798/// Compatibility shim for [`negative`].
1799#[generate_macro(customize(forwarding_shim = true))]
1800#[deprecated(
1801    since = "0.26.0",
1802    note = "use `with_stream` or `with_device` around `negative`"
1803)]
1804pub fn negative_device(
1805    a: impl AsRef<Array>,
1806    #[optional] stream: impl AsRef<Stream>,
1807) -> Result<Array> {
1808    crate::with_stream(stream.as_ref(), || negative(a))
1809}
1810
1811/// See [`Array::power`].
1812pub fn power(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1813    a.as_ref().power(b)
1814}
1815
1816/// Compatibility shim for [`power`].
1817#[generate_macro(customize(forwarding_shim = true))]
1818#[deprecated(
1819    since = "0.26.0",
1820    note = "use `with_stream` or `with_device` around `power`"
1821)]
1822pub fn power_device(
1823    a: impl AsRef<Array>,
1824    b: impl AsRef<Array>,
1825    #[optional] stream: impl AsRef<Stream>,
1826) -> Result<Array> {
1827    crate::with_stream(stream.as_ref(), || power(a, b))
1828}
1829
1830/// Convert angles from degrees to radians.
1831pub fn radians(a: impl AsRef<Array>) -> Result<Array> {
1832    let stream = Stream::thread_local_or_default();
1833    Array::try_from_op(|res| unsafe {
1834        mlx_sys::mlx_radians(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1835    })
1836}
1837
1838/// Compatibility shim for [`radians`].
1839#[generate_macro(customize(forwarding_shim = true))]
1840#[deprecated(
1841    since = "0.26.0",
1842    note = "use `with_stream` or `with_device` around `radians`"
1843)]
1844pub fn radians_device(
1845    a: impl AsRef<Array>,
1846    #[optional] stream: impl AsRef<Stream>,
1847) -> Result<Array> {
1848    crate::with_stream(stream.as_ref(), || radians(a))
1849}
1850
1851/// See [`Array::reciprocal`].
1852pub fn reciprocal(a: impl AsRef<Array>) -> Result<Array> {
1853    a.as_ref().reciprocal()
1854}
1855
1856/// Compatibility shim for [`reciprocal`].
1857#[generate_macro(customize(forwarding_shim = true))]
1858#[deprecated(
1859    since = "0.26.0",
1860    note = "use `with_stream` or `with_device` around `reciprocal`"
1861)]
1862pub fn reciprocal_device(
1863    a: impl AsRef<Array>,
1864    #[optional] stream: impl AsRef<Stream>,
1865) -> Result<Array> {
1866    crate::with_stream(stream.as_ref(), || reciprocal(a))
1867}
1868
1869/// See [`Array::remainder`].
1870pub fn remainder(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1871    a.as_ref().remainder(b)
1872}
1873
1874/// Compatibility shim for [`remainder`].
1875#[generate_macro(customize(forwarding_shim = true))]
1876#[deprecated(
1877    since = "0.26.0",
1878    note = "use `with_stream` or `with_device` around `remainder`"
1879)]
1880pub fn remainder_device(
1881    a: impl AsRef<Array>,
1882    b: impl AsRef<Array>,
1883    #[optional] stream: impl AsRef<Stream>,
1884) -> Result<Array> {
1885    crate::with_stream(stream.as_ref(), || remainder(a, b))
1886}
1887
1888/// See [`Array::round`].
1889pub fn round(a: impl AsRef<Array>, decimals: impl Into<Option<i32>>) -> Result<Array> {
1890    a.as_ref().round(decimals)
1891}
1892
1893/// Compatibility shim for [`round`].
1894#[generate_macro(customize(forwarding_shim = true))]
1895#[deprecated(
1896    since = "0.26.0",
1897    note = "use `with_stream` or `with_device` around `round`"
1898)]
1899pub fn round_device(
1900    a: impl AsRef<Array>,
1901    decimals: impl Into<Option<i32>>,
1902    #[optional] stream: impl AsRef<Stream>,
1903) -> Result<Array> {
1904    crate::with_stream(stream.as_ref(), || round(a, decimals))
1905}
1906
1907/// See [`Array::rsqrt`].
1908pub fn rsqrt(a: impl AsRef<Array>) -> Result<Array> {
1909    a.as_ref().rsqrt()
1910}
1911
1912/// Compatibility shim for [`rsqrt`].
1913#[generate_macro(customize(forwarding_shim = true))]
1914#[deprecated(
1915    since = "0.26.0",
1916    note = "use `with_stream` or `with_device` around `rsqrt`"
1917)]
1918pub fn rsqrt_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1919    crate::with_stream(stream.as_ref(), || rsqrt(a))
1920}
1921
1922/// Element-wise logistic sigmoid.
1923///
1924/// See the [python API
1925/// docs](https://ml-explore.github.io/mlx/build/html/python/_autosummary/mlx.core.sigmoid.html#mlx.core.sigmoid)
1926/// for more information
1927pub fn sigmoid(a: impl AsRef<Array>) -> Result<Array> {
1928    let stream = Stream::thread_local_or_default();
1929    Array::try_from_op(|res| unsafe {
1930        mlx_sys::mlx_sigmoid(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1931    })
1932}
1933
1934/// Compatibility shim for [`sigmoid`].
1935#[generate_macro(customize(forwarding_shim = true))]
1936#[deprecated(
1937    since = "0.26.0",
1938    note = "use `with_stream` or `with_device` around `sigmoid`"
1939)]
1940pub fn sigmoid_device(
1941    a: impl AsRef<Array>,
1942    #[optional] stream: impl AsRef<Stream>,
1943) -> Result<Array> {
1944    crate::with_stream(stream.as_ref(), || sigmoid(a))
1945}
1946
1947/// Element-wise sign.
1948pub fn sign(a: impl AsRef<Array>) -> Result<Array> {
1949    let stream = Stream::thread_local_or_default();
1950    Array::try_from_op(|res| unsafe {
1951        mlx_sys::mlx_sign(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1952    })
1953}
1954
1955/// Compatibility shim for [`sign`].
1956#[generate_macro(customize(forwarding_shim = true))]
1957#[deprecated(
1958    since = "0.26.0",
1959    note = "use `with_stream` or `with_device` around `sign`"
1960)]
1961pub fn sign_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1962    crate::with_stream(stream.as_ref(), || sign(a))
1963}
1964
1965/// See [`Array::sin`].
1966pub fn sin(a: impl AsRef<Array>) -> Result<Array> {
1967    a.as_ref().sin()
1968}
1969
1970/// Compatibility shim for [`sin`].
1971#[generate_macro(customize(forwarding_shim = true))]
1972#[deprecated(
1973    since = "0.26.0",
1974    note = "use `with_stream` or `with_device` around `sin`"
1975)]
1976pub fn sin_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1977    crate::with_stream(stream.as_ref(), || sin(a))
1978}
1979
1980/// Element-wise hyperbolic sine.
1981pub fn sinh(a: impl AsRef<Array>) -> Result<Array> {
1982    let stream = Stream::thread_local_or_default();
1983    Array::try_from_op(|res| unsafe {
1984        mlx_sys::mlx_sinh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1985    })
1986}
1987
1988/// Compatibility shim for [`sinh`].
1989#[generate_macro(customize(forwarding_shim = true))]
1990#[deprecated(
1991    since = "0.26.0",
1992    note = "use `with_stream` or `with_device` around `sinh`"
1993)]
1994pub fn sinh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1995    crate::with_stream(stream.as_ref(), || sinh(a))
1996}
1997
1998/// Perform the softmax along the given axis.
1999///
2000/// See the [python API
2001/// docs](https://ml-explore.github.io/mlx/build/html/python/_autosummary/mlx.core.softmax.html#mlx.core.softmax)
2002/// for more information.
2003pub fn softmax_axes(
2004    a: impl AsRef<Array>,
2005    axes: &[i32],
2006    precise: impl Into<Option<bool>>,
2007) -> Result<Array> {
2008    let stream = Stream::thread_local_or_default();
2009    let precise = precise.into().unwrap_or(false);
2010    let s = stream.as_ref().as_ptr();
2011
2012    Array::try_from_op(|res| unsafe {
2013        mlx_sys::mlx_softmax_axes(
2014            res,
2015            a.as_ref().as_ptr(),
2016            axes.as_ptr(),
2017            axes.len(),
2018            precise,
2019            s,
2020        )
2021    })
2022}
2023
2024/// Compatibility shim for [`softmax_axes`].
2025#[generate_macro(customize(forwarding_shim = true))]
2026#[deprecated(
2027    since = "0.26.0",
2028    note = "use `with_stream` or `with_device` around `softmax_axes`"
2029)]
2030pub fn softmax_axes_device(
2031    a: impl AsRef<Array>,
2032    axes: &[i32],
2033    precise: impl Into<Option<bool>>,
2034    #[optional] stream: impl AsRef<Stream>,
2035) -> Result<Array> {
2036    crate::with_stream(stream.as_ref(), || softmax_axes(a, axes, precise))
2037}
2038
2039/// Similar to [`softmax_axes`] but with a single axis.
2040pub fn softmax_axis(
2041    a: impl AsRef<Array>,
2042    axis: i32,
2043    precise: impl Into<Option<bool>>,
2044) -> Result<Array> {
2045    let stream = Stream::thread_local_or_default();
2046    let precise = precise.into().unwrap_or(false);
2047    let s = stream.as_ref().as_ptr();
2048
2049    Array::try_from_op(|res| unsafe {
2050        mlx_sys::mlx_softmax_axis(res, a.as_ref().as_ptr(), axis, precise, s)
2051    })
2052}
2053
2054/// Compatibility shim for [`softmax_axis`].
2055#[generate_macro(customize(forwarding_shim = true))]
2056#[deprecated(
2057    since = "0.26.0",
2058    note = "use `with_stream` or `with_device` around `softmax_axis`"
2059)]
2060pub fn softmax_axis_device(
2061    a: impl AsRef<Array>,
2062    axis: i32,
2063    precise: impl Into<Option<bool>>,
2064    #[optional] stream: impl AsRef<Stream>,
2065) -> Result<Array> {
2066    crate::with_stream(stream.as_ref(), || softmax_axis(a, axis, precise))
2067}
2068
2069/// Similar to [`softmax_axes`] but with no axis specified.
2070pub fn softmax(a: impl AsRef<Array>, precise: impl Into<Option<bool>>) -> Result<Array> {
2071    let stream = Stream::thread_local_or_default();
2072    let precise = precise.into().unwrap_or(false);
2073    let s = stream.as_ref().as_ptr();
2074
2075    Array::try_from_op(|res| unsafe { mlx_sys::mlx_softmax(res, a.as_ref().as_ptr(), precise, s) })
2076}
2077
2078/// Compatibility shim for [`softmax`].
2079#[generate_macro(customize(forwarding_shim = true))]
2080#[deprecated(
2081    since = "0.26.0",
2082    note = "use `with_stream` or `with_device` around `softmax`"
2083)]
2084pub fn softmax_device(
2085    a: impl AsRef<Array>,
2086    precise: impl Into<Option<bool>>,
2087    #[optional] stream: impl AsRef<Stream>,
2088) -> Result<Array> {
2089    crate::with_stream(stream.as_ref(), || softmax(a, precise))
2090}
2091
2092/// See [`Array::sqrt`].
2093pub fn sqrt(a: impl AsRef<Array>) -> Result<Array> {
2094    a.as_ref().sqrt()
2095}
2096
2097/// Compatibility shim for [`sqrt`].
2098#[generate_macro(customize(forwarding_shim = true))]
2099#[deprecated(
2100    since = "0.26.0",
2101    note = "use `with_stream` or `with_device` around `sqrt`"
2102)]
2103pub fn sqrt_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2104    crate::with_stream(stream.as_ref(), || sqrt(a))
2105}
2106
2107/// See [`Array::square`].
2108pub fn square(a: impl AsRef<Array>) -> Result<Array> {
2109    a.as_ref().square()
2110}
2111
2112/// Compatibility shim for [`square`].
2113#[generate_macro(customize(forwarding_shim = true))]
2114#[deprecated(
2115    since = "0.26.0",
2116    note = "use `with_stream` or `with_device` around `square`"
2117)]
2118pub fn square_device(
2119    a: impl AsRef<Array>,
2120    #[optional] stream: impl AsRef<Stream>,
2121) -> Result<Array> {
2122    crate::with_stream(stream.as_ref(), || square(a))
2123}
2124
2125/// See [`Array::subtract`].
2126pub fn subtract(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
2127    a.as_ref().subtract(b)
2128}
2129
2130/// Compatibility shim for [`subtract`].
2131#[generate_macro(customize(forwarding_shim = true))]
2132#[deprecated(
2133    since = "0.26.0",
2134    note = "use `with_stream` or `with_device` around `subtract`"
2135)]
2136pub fn subtract_device(
2137    a: impl AsRef<Array>,
2138    b: impl AsRef<Array>,
2139    #[optional] stream: impl AsRef<Stream>,
2140) -> Result<Array> {
2141    crate::with_stream(stream.as_ref(), || subtract(a, b))
2142}
2143
2144/// See [`Array::tan`].
2145pub fn tan(a: impl AsRef<Array>) -> Result<Array> {
2146    let stream = Stream::thread_local_or_default();
2147    Array::try_from_op(|res| unsafe {
2148        mlx_sys::mlx_tan(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2149    })
2150}
2151
2152/// Compatibility shim for [`tan`].
2153#[generate_macro(customize(forwarding_shim = true))]
2154#[deprecated(
2155    since = "0.26.0",
2156    note = "use `with_stream` or `with_device` around `tan`"
2157)]
2158pub fn tan_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2159    crate::with_stream(stream.as_ref(), || tan(a))
2160}
2161
2162/// Element-wise hyperbolic tangent.
2163pub fn tanh(a: impl AsRef<Array>) -> Result<Array> {
2164    let stream = Stream::thread_local_or_default();
2165    Array::try_from_op(|res| unsafe {
2166        mlx_sys::mlx_tanh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2167    })
2168}
2169
2170/// Compatibility shim for [`tanh`].
2171#[generate_macro(customize(forwarding_shim = true))]
2172#[deprecated(
2173    since = "0.26.0",
2174    note = "use `with_stream` or `with_device` around `tanh`"
2175)]
2176pub fn tanh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2177    crate::with_stream(stream.as_ref(), || tanh(a))
2178}
2179
2180/// Element-wise real part from a complex array.
2181pub fn real(a: impl AsRef<Array>) -> Result<Array> {
2182    let stream = Stream::thread_local_or_default();
2183    Array::try_from_op(|res| unsafe {
2184        mlx_sys::mlx_real(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2185    })
2186}
2187
2188/// Compatibility shim for [`real`].
2189#[generate_macro(customize(forwarding_shim = true))]
2190#[deprecated(
2191    since = "0.26.0",
2192    note = "use `with_stream` or `with_device` around `real`"
2193)]
2194pub fn real_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2195    crate::with_stream(stream.as_ref(), || real(a))
2196}
2197
2198/// Element-wise imaginary part from a complex array.
2199pub fn imag(a: impl AsRef<Array>) -> Result<Array> {
2200    let stream = Stream::thread_local_or_default();
2201    Array::try_from_op(|res| unsafe {
2202        mlx_sys::mlx_imag(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2203    })
2204}
2205
2206/// Compatibility shim for [`imag`].
2207#[generate_macro(customize(forwarding_shim = true))]
2208#[deprecated(
2209    since = "0.26.0",
2210    note = "use `with_stream` or `with_device` around `imag`"
2211)]
2212pub fn imag_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2213    crate::with_stream(stream.as_ref(), || imag(a))
2214}
2215
2216/// Matrix multiplication with block masking.
2217///
2218/// See the [python API docs](
2219/// https://ml-explore.github.io/mlx/build/html/python/_autosummary/mlx.core.block_masked_mm.html#mlx.core.block_masked_mm
2220/// ) for more information.
2221pub fn block_masked_mm<'mo, 'lhs, 'rhs>(
2222    a: impl AsRef<Array>,
2223    b: impl AsRef<Array>,
2224    block_size: impl Into<Option<i32>>,
2225    mask_out: impl Into<Option<&'mo Array>>,
2226    mask_lhs: impl Into<Option<&'lhs Array>>,
2227    mask_rhs: impl Into<Option<&'rhs Array>>,
2228) -> Result<Array> {
2229    let stream = Stream::thread_local_or_default();
2230    let a_ptr = a.as_ref().as_ptr();
2231    let b_ptr = b.as_ref().as_ptr();
2232    unsafe {
2233        let mask_out_ptr = mask_out
2234            .into()
2235            .map(|m| m.as_ptr())
2236            .unwrap_or(mlx_sys::mlx_array_new());
2237        let mask_lhs_ptr = mask_lhs
2238            .into()
2239            .map(|m| m.as_ptr())
2240            .unwrap_or(mlx_sys::mlx_array_new());
2241        let mask_rhs_ptr = mask_rhs
2242            .into()
2243            .map(|m| m.as_ptr())
2244            .unwrap_or(mlx_sys::mlx_array_new());
2245
2246        Array::try_from_op(|res| {
2247            mlx_sys::mlx_block_masked_mm(
2248                res,
2249                a_ptr,
2250                b_ptr,
2251                block_size.into().unwrap_or(32),
2252                mask_out_ptr,
2253                mask_lhs_ptr,
2254                mask_rhs_ptr,
2255                stream.as_ref().as_ptr(),
2256            )
2257        })
2258    }
2259}
2260
2261/// Compatibility shim for [`block_masked_mm`].
2262#[generate_macro(customize(forwarding_shim = true))]
2263#[deprecated(
2264    since = "0.26.0",
2265    note = "use `with_stream` or `with_device` around `block_masked_mm`"
2266)]
2267pub fn block_masked_mm_device<'mo, 'lhs, 'rhs>(
2268    a: impl AsRef<Array>,
2269    b: impl AsRef<Array>,
2270    #[optional] block_size: impl Into<Option<i32>>,
2271    #[optional] mask_out: impl Into<Option<&'mo Array>>,
2272    #[optional] mask_lhs: impl Into<Option<&'lhs Array>>,
2273    #[optional] mask_rhs: impl Into<Option<&'rhs Array>>,
2274    #[optional] stream: impl AsRef<Stream>,
2275) -> Result<Array> {
2276    crate::with_stream(stream.as_ref(), || {
2277        block_masked_mm(a, b, block_size, mask_out, mask_lhs, mask_rhs)
2278    })
2279}
2280
2281/// Matrix multiplication with addition and optional scaling.
2282///
2283/// Perform the (possibly batched) matrix multiplication of two arrays and add to the result with
2284/// optional scaling factors.
2285///
2286/// # Params
2287///
2288/// - `c`: input array,
2289/// - `a`: input array,
2290/// - `b`: input array,
2291/// - `alpha`: Scaling factor for the matrix product of `a` and `b` (default: `1`)
2292/// - `beta`: Scaling factor for `c` (default: `1`)
2293pub fn addmm(
2294    c: impl AsRef<Array>,
2295    a: impl AsRef<Array>,
2296    b: impl AsRef<Array>,
2297    alpha: impl Into<Option<f32>>,
2298    beta: impl Into<Option<f32>>,
2299) -> Result<Array> {
2300    let stream = Stream::thread_local_or_default();
2301    let c_ptr = c.as_ref().as_ptr();
2302    let a_ptr = a.as_ref().as_ptr();
2303    let b_ptr = b.as_ref().as_ptr();
2304    let alpha = alpha.into().unwrap_or(1.0);
2305    let beta = beta.into().unwrap_or(1.0);
2306
2307    Array::try_from_op(|res| unsafe {
2308        mlx_sys::mlx_addmm(
2309            res,
2310            c_ptr,
2311            a_ptr,
2312            b_ptr,
2313            alpha,
2314            beta,
2315            stream.as_ref().as_ptr(),
2316        )
2317    })
2318}
2319
2320/// Compatibility shim for [`addmm`].
2321#[generate_macro(customize(forwarding_shim = true))]
2322#[deprecated(
2323    since = "0.26.0",
2324    note = "use `with_stream` or `with_device` around `addmm`"
2325)]
2326pub fn addmm_device(
2327    c: impl AsRef<Array>,
2328    a: impl AsRef<Array>,
2329    b: impl AsRef<Array>,
2330    #[optional] alpha: impl Into<Option<f32>>,
2331    #[optional] beta: impl Into<Option<f32>>,
2332    #[optional] stream: impl AsRef<Stream>,
2333) -> Result<Array> {
2334    crate::with_stream(stream.as_ref(), || addmm(c, a, b, alpha, beta))
2335}
2336
2337/// Ordinary inner product of vectors for 1-D arrays, in higher dimensions a sum product over the
2338/// last axes.
2339pub fn inner(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
2340    let stream = Stream::thread_local_or_default();
2341    let a = a.as_ref();
2342    let b = b.as_ref();
2343    Array::try_from_op(|res| unsafe {
2344        mlx_sys::mlx_inner(res, a.as_ptr(), b.as_ptr(), stream.as_ref().as_ptr())
2345    })
2346}
2347
2348/// Compatibility shim for [`inner`].
2349#[generate_macro(customize(forwarding_shim = true))]
2350#[deprecated(
2351    since = "0.26.0",
2352    note = "use `with_stream` or `with_device` around `inner`"
2353)]
2354pub fn inner_device(
2355    a: impl AsRef<Array>,
2356    b: impl AsRef<Array>,
2357    #[optional] stream: impl AsRef<Stream>,
2358) -> Result<Array> {
2359    crate::with_stream(stream.as_ref(), || inner(a, b))
2360}
2361
2362/// Compute the outer product of two 1-D arrays, if the array’s passed are not 1-D a flatten op will
2363/// be run beforehand.
2364pub fn outer(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
2365    let stream = Stream::thread_local_or_default();
2366    let a = a.as_ref();
2367    let b = b.as_ref();
2368    Array::try_from_op(|res| unsafe {
2369        mlx_sys::mlx_outer(res, a.as_ptr(), b.as_ptr(), stream.as_ref().as_ptr())
2370    })
2371}
2372
2373/// Compatibility shim for [`outer`].
2374#[generate_macro(customize(forwarding_shim = true))]
2375#[deprecated(
2376    since = "0.26.0",
2377    note = "use `with_stream` or `with_device` around `outer`"
2378)]
2379pub fn outer_device(
2380    a: impl AsRef<Array>,
2381    b: impl AsRef<Array>,
2382    #[optional] stream: impl AsRef<Stream>,
2383) -> Result<Array> {
2384    crate::with_stream(stream.as_ref(), || outer(a, b))
2385}
2386
2387/// Compute the tensor dot product along the specified axes.
2388pub fn tensordot_axes(
2389    a: impl AsRef<Array>,
2390    b: impl AsRef<Array>,
2391    axes_a: &[i32],
2392    axes_b: &[i32],
2393) -> Result<Array> {
2394    let stream = Stream::thread_local_or_default();
2395    let a = a.as_ref();
2396    let b = b.as_ref();
2397    Array::try_from_op(|res| unsafe {
2398        mlx_sys::mlx_tensordot(
2399            res,
2400            a.as_ptr(),
2401            b.as_ptr(),
2402            axes_a.as_ptr(),
2403            axes_a.len(),
2404            axes_b.as_ptr(),
2405            axes_b.len(),
2406            stream.as_ref().as_ptr(),
2407        )
2408    })
2409}
2410
2411/// Compatibility shim for [`tensordot_axes`].
2412#[generate_macro(customize(forwarding_shim = true))]
2413#[deprecated(
2414    since = "0.26.0",
2415    note = "use `with_stream` or `with_device` around `tensordot_axes`"
2416)]
2417pub fn tensordot_axes_device(
2418    a: impl AsRef<Array>,
2419    b: impl AsRef<Array>,
2420    axes_a: &[i32],
2421    axes_b: &[i32],
2422    #[optional] stream: impl AsRef<Stream>,
2423) -> Result<Array> {
2424    crate::with_stream(stream.as_ref(), || tensordot_axes(a, b, axes_a, axes_b))
2425}
2426
2427/// Similar to [`tensordot_axes`] but with a single axis.
2428pub fn tensordot_axis(a: impl AsRef<Array>, b: impl AsRef<Array>, axis: i32) -> Result<Array> {
2429    let stream = Stream::thread_local_or_default();
2430    let a = a.as_ref();
2431    let b = b.as_ref();
2432    Array::try_from_op(|res| unsafe {
2433        mlx_sys::mlx_tensordot_axis(res, a.as_ptr(), b.as_ptr(), axis, stream.as_ref().as_ptr())
2434    })
2435}
2436
2437/// Compatibility shim for [`tensordot_axis`].
2438#[generate_macro(customize(forwarding_shim = true))]
2439#[deprecated(
2440    since = "0.26.0",
2441    note = "use `with_stream` or `with_device` around `tensordot_axis`"
2442)]
2443pub fn tensordot_axis_device(
2444    a: impl AsRef<Array>,
2445    b: impl AsRef<Array>,
2446    axis: i32,
2447    #[optional] stream: impl AsRef<Stream>,
2448) -> Result<Array> {
2449    crate::with_stream(stream.as_ref(), || tensordot_axis(a, b, axis))
2450}
2451
2452/// Matrix multiplication with gathered indices.
2453///
2454/// Perform matrix multiplication with index gathering along the batch dimensions.
2455/// This is useful for operations where different batch elements should use different
2456/// matrices from a pool.
2457///
2458/// # Params
2459///
2460/// - `a`: Input array
2461/// - `b`: Input array
2462/// - `lhs_indices`: Optional indices to gather from `a`'s batch dimensions
2463/// - `rhs_indices`: Optional indices to gather from `b`'s batch dimensions
2464/// - `sorted_indices`: If true, indicates the indices are sorted which can enable
2465///   optimizations (default: false)
2466///
2467/// # Example
2468///
2469/// ```rust
2470/// use mlx_rs::{Array, ops::gather_mm};
2471///
2472/// let a = Array::ones::<f32>(&[5, 32, 32]).unwrap();
2473/// let b = Array::ones::<f32>(&[3, 32, 32]).unwrap();
2474/// let lhs_indices = Array::from_slice(&[0u32, 2], &[2]);
2475/// let rhs_indices = Array::from_slice(&[2u32, 1], &[2]);
2476/// let result = gather_mm(&a, &b, &lhs_indices, &rhs_indices, None).unwrap();
2477/// // result has shape [2, 32, 32]
2478/// ```
2479pub fn gather_mm<'lhs, 'rhs>(
2480    a: impl AsRef<Array>,
2481    b: impl AsRef<Array>,
2482    lhs_indices: impl Into<Option<&'lhs Array>>,
2483    rhs_indices: impl Into<Option<&'rhs Array>>,
2484    sorted_indices: impl Into<Option<bool>>,
2485) -> Result<Array> {
2486    let stream = Stream::thread_local_or_default();
2487    let a_ptr = a.as_ref().as_ptr();
2488    let b_ptr = b.as_ref().as_ptr();
2489    let sorted = sorted_indices.into().unwrap_or(false);
2490
2491    unsafe {
2492        let lhs_ptr = lhs_indices
2493            .into()
2494            .map(|i| i.as_ptr())
2495            .unwrap_or(mlx_sys::mlx_array_new());
2496        let rhs_ptr = rhs_indices
2497            .into()
2498            .map(|i| i.as_ptr())
2499            .unwrap_or(mlx_sys::mlx_array_new());
2500
2501        Array::try_from_op(|res| {
2502            mlx_sys::mlx_gather_mm(
2503                res,
2504                a_ptr,
2505                b_ptr,
2506                lhs_ptr,
2507                rhs_ptr,
2508                sorted,
2509                stream.as_ref().as_ptr(),
2510            )
2511        })
2512    }
2513}
2514
2515/// Compatibility shim for [`gather_mm`].
2516#[generate_macro(customize(forwarding_shim = true))]
2517#[deprecated(
2518    since = "0.26.0",
2519    note = "use `with_stream` or `with_device` around `gather_mm`"
2520)]
2521pub fn gather_mm_device<'lhs, 'rhs>(
2522    a: impl AsRef<Array>,
2523    b: impl AsRef<Array>,
2524    #[optional] lhs_indices: impl Into<Option<&'lhs Array>>,
2525    #[optional] rhs_indices: impl Into<Option<&'rhs Array>>,
2526    #[optional] sorted_indices: impl Into<Option<bool>>,
2527    #[optional] stream: impl AsRef<Stream>,
2528) -> Result<Array> {
2529    crate::with_stream(stream.as_ref(), || {
2530        gather_mm(a, b, lhs_indices, rhs_indices, sorted_indices)
2531    })
2532}
2533
2534#[cfg(test)]
2535mod tests {
2536    use std::f32::consts::PI;
2537
2538    use super::*;
2539    use crate::{
2540        array, complex64,
2541        ops::{all_close, arange, broadcast_to, eye, full, linspace, ones, reshape, split_equal},
2542        test_utils::{assert_array_eq, assert_array_eq_with_context, tolerances},
2543        transforms::eval,
2544        Dtype,
2545    };
2546    use float_eq::assert_float_eq;
2547    use pretty_assertions::assert_eq;
2548
2549    #[test]
2550    fn test_abs() {
2551        let data = [1i32, 2, -3, -4, -5];
2552        let array = Array::from_slice(&data, &[5]);
2553        let result = array.abs().unwrap();
2554
2555        let data: &[i32] = result.as_slice();
2556        assert_eq!(data, [1, 2, 3, 4, 5]);
2557
2558        // test that previous array is not modified and valid
2559        let data: &[i32] = array.as_slice();
2560        assert_eq!(data, [1, 2, -3, -4, -5]);
2561    }
2562
2563    #[test]
2564    fn test_add() {
2565        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2566        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2567
2568        let c = &a + &b;
2569
2570        let c_data: &[f32] = c.as_slice();
2571        assert_eq!(c_data, &[5.0, 7.0, 9.0]);
2572
2573        // check a and b are not modified
2574        let a_data: &[f32] = a.as_slice();
2575        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2576
2577        let b_data: &[f32] = b.as_slice();
2578        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2579    }
2580
2581    #[test]
2582    fn test_add_invalid_broadcast() {
2583        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2584        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2585
2586        let c = a.add(&b);
2587        assert!(c.is_err());
2588    }
2589
2590    #[test]
2591    fn test_sub() {
2592        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2593        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2594
2595        let c = &a - &b;
2596
2597        let c_data: &[f32] = c.as_slice();
2598        assert_eq!(c_data, &[-3.0, -3.0, -3.0]);
2599
2600        // check a and b are not modified
2601        let a_data: &[f32] = a.as_slice();
2602        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2603
2604        let b_data: &[f32] = b.as_slice();
2605        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2606    }
2607
2608    #[test]
2609    fn test_sub_invalid_broadcast() {
2610        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2611        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2612        let c = a.subtract(&b);
2613        assert!(c.is_err());
2614    }
2615
2616    #[test]
2617    fn test_neg() {
2618        let a = Array::from_slice::<f32>(&[1.0, 2.0, 3.0], &[3]);
2619        let b = a.negative().unwrap();
2620
2621        let b_data: &[f32] = b.as_slice();
2622        assert_eq!(b_data, &[-1.0, -2.0, -3.0]);
2623
2624        // check a is not modified
2625        let a_data: &[f32] = a.as_slice();
2626        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2627    }
2628
2629    #[test]
2630    fn test_neg_bool() {
2631        let a = Array::from_slice(&[true, false, true], &[3]);
2632        let b = a.negative();
2633        assert!(b.is_err());
2634    }
2635
2636    #[test]
2637    fn test_logical_not() {
2638        let a: Array = false.into();
2639        let b = a.logical_not().unwrap();
2640
2641        let b_data: &[bool] = b.as_slice();
2642        assert_eq!(b_data, [true]);
2643    }
2644
2645    #[test]
2646    fn test_mul() {
2647        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2648        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2649
2650        let c = &a * &b;
2651
2652        let c_data: &[f32] = c.as_slice();
2653        assert_eq!(c_data, &[4.0, 10.0, 18.0]);
2654
2655        // check a and b are not modified
2656        let a_data: &[f32] = a.as_slice();
2657        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2658
2659        let b_data: &[f32] = b.as_slice();
2660        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2661    }
2662
2663    #[test]
2664    fn test_mul_invalid_broadcast() {
2665        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2666        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2667        let c = a.multiply(&b);
2668        assert!(c.is_err());
2669    }
2670
2671    #[test]
2672    fn test_nan_to_num() {
2673        let a = array!([1.0, 2.0, f32::NAN, 4.0, 5.0]);
2674        let b = a.nan_to_num(0.0, 1.0, 0.0).unwrap();
2675
2676        let b_data: &[f32] = b.as_slice();
2677        assert_eq!(b_data, &[1.0, 2.0, 0.0, 4.0, 5.0]);
2678    }
2679
2680    #[test]
2681    fn test_div() {
2682        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2683        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2684
2685        let c = &a / &b;
2686
2687        let c_data: &[f32] = c.as_slice();
2688        assert_eq!(c_data, &[0.25, 0.4, 0.5]);
2689
2690        // check a and b are not modified
2691        let a_data: &[f32] = a.as_slice();
2692        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2693
2694        let b_data: &[f32] = b.as_slice();
2695        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2696    }
2697
2698    #[test]
2699    fn test_div_invalid_broadcast() {
2700        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2701        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2702        let c = a.divide(&b);
2703        assert!(c.is_err());
2704    }
2705
2706    #[test]
2707    fn test_pow() {
2708        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2709        let b = Array::from_slice(&[2.0, 3.0, 4.0], &[3]);
2710
2711        let c = a.power(&b).unwrap();
2712
2713        let c_data: &[f32] = c.as_slice();
2714        assert_eq!(c_data, &[1.0, 8.0, 81.0]);
2715
2716        // check a and b are not modified
2717        let a_data: &[f32] = a.as_slice();
2718        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2719
2720        let b_data: &[f32] = b.as_slice();
2721        assert_eq!(b_data, &[2.0, 3.0, 4.0]);
2722    }
2723
2724    #[test]
2725    fn test_pow_invalid_broadcast() {
2726        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2727        let b = Array::from_slice(&[2.0, 3.0], &[2]);
2728        let c = a.power(&b);
2729        assert!(c.is_err());
2730    }
2731
2732    #[test]
2733    fn test_rem() {
2734        let a = Array::from_slice(&[10.0, 11.0, 12.0], &[3]);
2735        let b = Array::from_slice(&[3.0, 4.0, 5.0], &[3]);
2736
2737        let c = &a % &b;
2738
2739        let c_data: &[f32] = c.as_slice();
2740        assert_eq!(c_data, &[1.0, 3.0, 2.0]);
2741
2742        // check a and b are not modified
2743        let a_data: &[f32] = a.as_slice();
2744        assert_eq!(a_data, &[10.0, 11.0, 12.0]);
2745
2746        let b_data: &[f32] = b.as_slice();
2747        assert_eq!(b_data, &[3.0, 4.0, 5.0]);
2748    }
2749
2750    #[test]
2751    fn test_rem_invalid_broadcast() {
2752        let a = Array::from_slice(&[10.0, 11.0, 12.0], &[3]);
2753        let b = Array::from_slice(&[3.0, 4.0], &[2]);
2754        let c = a.remainder(&b);
2755        assert!(c.is_err());
2756    }
2757
2758    #[test]
2759    fn test_sqrt() {
2760        let a = Array::from_slice(&[1.0, 4.0, 9.0], &[3]);
2761        let b = a.sqrt().unwrap();
2762
2763        let b_data: &[f32] = b.as_slice();
2764        assert_eq!(b_data, &[1.0, 2.0, 3.0]);
2765
2766        // check a is not modified
2767        let a_data: &[f32] = a.as_slice();
2768        assert_eq!(a_data, &[1.0, 4.0, 9.0]);
2769    }
2770
2771    #[test]
2772    fn test_cos() {
2773        let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
2774        let b = a.cos().unwrap();
2775
2776        let b_expected = array!([1.0, 0.54030234, -0.41614687]);
2777        assert_array_all_close!(b, b_expected);
2778
2779        // check a is not modified
2780        let a_expected = array!([0.0, 1.0, 2.0]);
2781        assert_array_all_close!(a, a_expected);
2782    }
2783
2784    #[test]
2785    fn test_exp() {
2786        let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
2787        let b = a.exp().unwrap();
2788
2789        let b_expected = array!([1.0, 2.7182817, 7.389056]);
2790        assert_array_all_close!(b, b_expected);
2791
2792        // check a is not modified
2793        let a_expected = array!([0.0, 1.0, 2.0]);
2794        assert_array_all_close!(a, a_expected);
2795    }
2796
2797    #[test]
2798    fn test_floor() {
2799        let a = Array::from_slice(&[0.1, 1.9, 2.5], &[3]);
2800        let b = a.floor().unwrap();
2801
2802        let b_data: &[f32] = b.as_slice();
2803        assert_eq!(b_data, &[0.0, 1.0, 2.0]);
2804
2805        // check a is not modified
2806        let a_data: &[f32] = a.as_slice();
2807        assert_eq!(a_data, &[0.1, 1.9, 2.5]);
2808    }
2809
2810    #[test]
2811    fn test_floor_complex64() {
2812        let val = complex64::new(1.0, 2.0);
2813        let a = Array::from_complex(val);
2814        let b = a.floor();
2815        assert!(b.is_err());
2816    }
2817
2818    #[test]
2819    fn test_floor_divide() {
2820        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2821        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2822
2823        let c = a.floor_divide(&b).unwrap();
2824
2825        let c_data: &[f32] = c.as_slice();
2826        assert_eq!(c_data, &[0.0, 0.0, 0.0]);
2827
2828        // check a and b are not modified
2829        let a_data: &[f32] = a.as_slice();
2830        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2831
2832        let b_data: &[f32] = b.as_slice();
2833        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2834    }
2835
2836    #[test]
2837    fn test_floor_divide_complex64() {
2838        let val = complex64::new(1.0, 2.0);
2839        let a = Array::from_complex(val);
2840        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2841        let c = a.floor_divide(&b);
2842        assert!(c.is_err());
2843    }
2844
2845    #[test]
2846    fn test_floor_divide_invalid_broadcast() {
2847        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2848        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2849        let c = a.floor_divide(&b);
2850        assert!(c.is_err());
2851    }
2852
2853    #[test]
2854    fn test_is_nan() {
2855        let a = Array::from_slice(&[1.0, f32::NAN, 3.0], &[3]);
2856        let b = a.is_nan().unwrap();
2857
2858        let b_data: &[bool] = b.as_slice();
2859        assert_eq!(b_data, &[false, true, false]);
2860    }
2861
2862    #[test]
2863    fn test_is_inf() {
2864        let a = Array::from_slice(&[1.0, f32::INFINITY, 3.0], &[3]);
2865        let b = a.is_inf().unwrap();
2866
2867        let b_data: &[bool] = b.as_slice();
2868        assert_eq!(b_data, &[false, true, false]);
2869    }
2870
2871    #[test]
2872    fn test_is_finite() {
2873        let a = Array::from_slice(&[1.0, f32::INFINITY, 3.0], &[3]);
2874        let b = a.is_finite().unwrap();
2875
2876        let b_data: &[bool] = b.as_slice();
2877        assert_eq!(b_data, &[true, false, true]);
2878    }
2879
2880    #[test]
2881    fn test_is_neg_inf() {
2882        let a = Array::from_slice(&[1.0, f32::NEG_INFINITY, 3.0], &[3]);
2883        let b = a.is_neg_inf().unwrap();
2884
2885        let b_data: &[bool] = b.as_slice();
2886        assert_eq!(b_data, &[false, true, false]);
2887    }
2888
2889    #[test]
2890    fn test_is_pos_inf() {
2891        let a = Array::from_slice(&[1.0, f32::INFINITY, 3.0], &[3]);
2892        let b = a.is_pos_inf().unwrap();
2893
2894        let b_data: &[bool] = b.as_slice();
2895        assert_eq!(b_data, &[false, true, false]);
2896    }
2897
2898    #[test]
2899    fn test_log() {
2900        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2901        let b = a.log().unwrap();
2902
2903        let b_data: &[f32] = b.as_slice();
2904        assert_eq!(b_data, &[0.0, 0.6931472, 1.0986123]);
2905
2906        // check a is not modified
2907        let a_data: &[f32] = a.as_slice();
2908        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2909    }
2910
2911    #[test]
2912    fn test_log2() {
2913        let a = Array::from_slice(&[1.0, 2.0, 4.0, 8.0], &[4]);
2914        let b = a.log2().unwrap();
2915
2916        let b_data: &[f32] = b.as_slice();
2917        assert_eq!(b_data, &[0.0, 1.0, 2.0, 3.0]);
2918
2919        // check a is not modified
2920        let a_data: &[f32] = a.as_slice();
2921        assert_eq!(a_data, &[1.0, 2.0, 4.0, 8.0]);
2922    }
2923
2924    #[test]
2925    fn test_log10() {
2926        let a = Array::from_slice(&[1.0, 10.0, 100.0], &[3]);
2927        let b = a.log10().unwrap();
2928
2929        let b_data: &[f32] = b.as_slice();
2930        assert_eq!(b_data, &[0.0, 1.0, 2.0]);
2931
2932        // check a is not modified
2933        let a_data: &[f32] = a.as_slice();
2934        assert_eq!(a_data, &[1.0, 10.0, 100.0]);
2935    }
2936
2937    #[test]
2938    fn test_log1p() {
2939        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2940        let b = a.log1p().unwrap();
2941
2942        let b_data: &[f32] = b.as_slice();
2943        assert_eq!(b_data, &[0.6931472, 1.0986123, 1.3862944]);
2944
2945        // check a is not modified
2946        let a_data: &[f32] = a.as_slice();
2947        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2948    }
2949
2950    #[test]
2951    fn test_matmul() {
2952        let a = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
2953        let b = Array::from_slice(&[-5.0, 37.5, 4., 7., 1., 0.], &[2, 3]);
2954
2955        let c = a.matmul(&b).unwrap();
2956
2957        assert_eq!(c.shape(), &[2, 3]);
2958        let c_data: &[f32] = c.as_slice();
2959        assert_eq!(c_data, &[9.0, 39.5, 4.0, 13.0, 116.5, 12.0]);
2960
2961        // check a and b are not modified
2962        let a_data: &[i32] = a.as_slice();
2963        assert_eq!(a_data, &[1, 2, 3, 4]);
2964
2965        let b_data: &[f32] = b.as_slice();
2966        assert_eq!(b_data, &[-5.0, 37.5, 4., 7., 1., 0.]);
2967    }
2968
2969    #[test]
2970    fn test_matmul_ndim_zero() {
2971        let a: Array = 1.0.into();
2972        let b = Array::from_slice::<i32>(&[1], &[1]);
2973        let c = a.matmul(&b);
2974        assert!(c.is_err());
2975    }
2976
2977    #[test]
2978    fn test_matmul_ndim_one() {
2979        let a = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
2980        let b = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
2981        let c = a.matmul(&b);
2982        assert!(c.is_ok());
2983    }
2984
2985    #[test]
2986    fn test_matmul_dim_mismatch() {
2987        let a = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
2988        let b = Array::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], &[2, 5]);
2989        let c = a.matmul(&b);
2990        assert!(c.is_err());
2991    }
2992
2993    #[test]
2994    fn test_matmul_non_float_output_type() {
2995        let a = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
2996        let b = Array::from_slice(&[5, 37, 4, 7, 1, 0], &[2, 3]);
2997
2998        let c = a.matmul(&b);
2999        assert!(c.is_err());
3000    }
3001
3002    #[test]
3003    fn test_reciprocal() {
3004        let a = Array::from_slice(&[1.0, 2.0, 4.0], &[3]);
3005        let b = a.reciprocal().unwrap();
3006
3007        let b_data: &[f32] = b.as_slice();
3008        assert_eq!(b_data, &[1.0, 0.5, 0.25]);
3009
3010        // check a is not modified
3011        let a_data: &[f32] = a.as_slice();
3012        assert_eq!(a_data, &[1.0, 2.0, 4.0]);
3013    }
3014
3015    #[test]
3016    fn test_round() {
3017        let a = Array::from_slice(&[1.1, 2.9, 3.5], &[3]);
3018        let b = a.round(None).unwrap();
3019
3020        let b_data: &[f32] = b.as_slice();
3021        assert_eq!(b_data, &[1.0, 3.0, 4.0]);
3022
3023        // check a is not modified
3024        let a_data: &[f32] = a.as_slice();
3025        assert_eq!(a_data, &[1.1, 2.9, 3.5]);
3026    }
3027
3028    #[test]
3029    fn test_rsqrt() {
3030        let a = Array::from_slice(&[1.0, 2.0, 4.0], &[3]);
3031        let b = a.rsqrt().unwrap();
3032
3033        let b_data: &[f32] = b.as_slice();
3034        assert_eq!(b_data, &[1.0, 0.70710677, 0.5]);
3035
3036        // check a is not modified
3037        let a_data: &[f32] = a.as_slice();
3038        assert_eq!(a_data, &[1.0, 2.0, 4.0]);
3039    }
3040
3041    #[test]
3042    fn test_sin() {
3043        let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
3044        let b = a.sin().unwrap();
3045
3046        let b_data: &[f32] = b.as_slice();
3047        assert_eq!(b_data, &[0.0, 0.841471, 0.9092974]);
3048
3049        // check a is not modified
3050        let a_data: &[f32] = a.as_slice();
3051        assert_eq!(a_data, &[0.0, 1.0, 2.0]);
3052    }
3053
3054    #[test]
3055    fn test_square() {
3056        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
3057        let b = a.square().unwrap();
3058
3059        let b_data: &[f32] = b.as_slice();
3060        assert_eq!(b_data, &[1.0, 4.0, 9.0]);
3061
3062        // check a is not modified
3063        let a_data: &[f32] = a.as_slice();
3064        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
3065    }
3066
3067    // The unit tests below are adapted from the original mlx c++ codebase.
3068
3069    #[test]
3070    fn test_unary_neg() {
3071        let x = array!(1.0);
3072        assert_eq!(negative(&x).unwrap().item_exact::<f32>(), -1.0);
3073        assert_eq!((-x).item_exact::<f32>(), -1.0);
3074
3075        // works on empty array
3076        assert_array_eq(
3077            -array!(),
3078            array!(),
3079            tolerances::EXACT.rtol,
3080            tolerances::EXACT.atol,
3081        );
3082
3083        // Throws on bool
3084        let x = array!(true);
3085        assert!(negative(&x).is_err());
3086    }
3087
3088    #[test]
3089    fn test_unary_abs() {
3090        let x = array!([-1.0, 0.0, 1.0]);
3091        assert_array_eq(
3092            abs(&x).unwrap(),
3093            array!([1.0, 0.0, 1.0]),
3094            tolerances::EXACT.rtol,
3095            tolerances::EXACT.atol,
3096        );
3097
3098        // works on empty array
3099        assert_array_eq(
3100            abs(array!()).unwrap(),
3101            array!(),
3102            tolerances::EXACT.rtol,
3103            tolerances::EXACT.atol,
3104        );
3105
3106        // int32
3107        let x = array!([-1, 0, 1]);
3108        assert_array_eq(
3109            abs(&x).unwrap(),
3110            array!([1, 0, 1]),
3111            tolerances::EXACT.rtol,
3112            tolerances::EXACT.atol,
3113        );
3114
3115        // uint32
3116        let x = array!([1u32, 0, 1]);
3117        assert_array_eq(
3118            abs(&x).unwrap(),
3119            array!([1u32, 0, 1]),
3120            tolerances::EXACT.rtol,
3121            tolerances::EXACT.atol,
3122        );
3123
3124        // bool
3125        let x = array!([false, true]);
3126        assert_array_eq(
3127            abs(&x).unwrap(),
3128            array!([false, true]),
3129            tolerances::EXACT.rtol,
3130            tolerances::EXACT.atol,
3131        );
3132    }
3133
3134    #[test]
3135    fn test_unary_sign() {
3136        let x = array!([-1.0, 0.0, 1.0]);
3137        assert_array_eq(
3138            sign(&x).unwrap(),
3139            x,
3140            tolerances::EXACT.rtol,
3141            tolerances::EXACT.atol,
3142        );
3143
3144        // works on empty array
3145        assert_array_eq(
3146            sign(array!()).unwrap(),
3147            array!(),
3148            tolerances::EXACT.rtol,
3149            tolerances::EXACT.atol,
3150        );
3151
3152        // int32
3153        let x = array!([-1, 0, 1]);
3154        assert_array_eq(
3155            sign(&x).unwrap(),
3156            x,
3157            tolerances::EXACT.rtol,
3158            tolerances::EXACT.atol,
3159        );
3160
3161        // uint32
3162        let x = array!([1u32, 0, 1]);
3163        assert_array_eq(
3164            sign(&x).unwrap(),
3165            x,
3166            tolerances::EXACT.rtol,
3167            tolerances::EXACT.atol,
3168        );
3169
3170        // bool
3171        let x = array!([false, true]);
3172        assert_array_eq(
3173            sign(&x).unwrap(),
3174            x,
3175            tolerances::EXACT.rtol,
3176            tolerances::EXACT.atol,
3177        );
3178    }
3179
3180    const NEG_INF: f32 = f32::NEG_INFINITY;
3181
3182    #[test]
3183    fn test_unary_floor_ceil() {
3184        let x = array![1.0];
3185        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), 1.0);
3186        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), 1.0);
3187
3188        let x = array![1.5];
3189        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), 1.0);
3190        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), 2.0);
3191
3192        let x = array![-1.5];
3193        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), -2.0);
3194        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), -1.0);
3195
3196        let x = array![NEG_INF];
3197        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), NEG_INF);
3198        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), NEG_INF);
3199
3200        let x = array!([1.0, 1.0]).as_type::<complex64>().unwrap();
3201        assert!(floor(&x).is_err());
3202        assert!(ceil(&x).is_err());
3203    }
3204
3205    #[test]
3206    fn test_unary_round() {
3207        let x = array!([0.5, -0.5, 1.5, -1.5, 2.3, 2.6]);
3208        assert_array_eq_with_context(
3209            round(&x, None).unwrap(),
3210            array!([0.0_f32, 0.0, 2.0, -2.0, 2.0, 3.0]),
3211            tolerances::EXACT.rtol,
3212            tolerances::EXACT.atol,
3213            "float32 half-to-even rounding",
3214        );
3215
3216        let x = array!([11, 222, 32]);
3217        assert_array_eq_with_context(
3218            round(&x, -1).unwrap(),
3219            array!([10, 220, 30]),
3220            tolerances::EXACT.rtol,
3221            tolerances::EXACT.atol,
3222            "int32 negative-decimal rounding",
3223        );
3224    }
3225
3226    #[test]
3227    fn test_unary_exp() {
3228        let x = array![0.0];
3229        assert_eq!(exp(&x).unwrap().item_exact::<f32>(), 1.0);
3230
3231        let x = array![2.0];
3232        assert_float_eq! {
3233            exp(&x).unwrap().item_exact::<f32>(),
3234            2.0f32.exp(),
3235            abs <= 1e-5
3236        };
3237
3238        assert_array_eq(
3239            exp(array!()).unwrap(),
3240            array!(),
3241            tolerances::EXACT.rtol,
3242            tolerances::EXACT.atol,
3243        );
3244
3245        let x = array![NEG_INF];
3246        assert_eq!(exp(&x).unwrap().item_exact::<f32>(), 0.0);
3247
3248        // Integer input type
3249        let x = array![2];
3250        assert_eq!(x.dtype(), Dtype::Int32);
3251        assert_float_eq! {
3252            exp(&x).unwrap().item_exact::<f32>(),
3253            2.0f32.exp(),
3254            abs <= 1e-5
3255        };
3256
3257        // Input is irregularly strided
3258        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3259        let res = exp(&x).unwrap();
3260        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.exp())).unwrap();
3261        assert!(all_close(&res, &expected, None, None, None).unwrap());
3262
3263        let data = Array::from_slice(&[0.0, 1.0, 2.0, 3.0], &[2, 2]);
3264        let x = split_equal(&data, 2, 1).unwrap();
3265        let expected = Array::from_slice(&[0.0f32.exp(), 2.0f32.exp()], &[2, 1]);
3266        assert!(all_close(exp(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3267    }
3268
3269    #[test]
3270    fn test_unary_expm1() {
3271        let x = array![-1.0];
3272        assert_float_eq! {
3273            expm1(&x).unwrap().item_exact::<f32>(),
3274            (-1.0f32).exp_m1(),
3275            abs <= 1e-5
3276        };
3277
3278        let x = array![1.0];
3279        assert_float_eq! {
3280            expm1(&x).unwrap().item_exact::<f32>(),
3281            1.0f32.exp_m1(),
3282            abs <= 1e-5
3283        };
3284
3285        // Integer input type
3286        let x = array![1];
3287        assert_eq!(expm1(&x).unwrap().dtype(), Dtype::Float32);
3288        assert_float_eq! {
3289            expm1(&x).unwrap().item_exact::<f32>(),
3290            1.0f32.exp_m1(),
3291            abs <= 1e-5
3292        };
3293    }
3294
3295    #[test]
3296    fn test_unary_sin() {
3297        let x = array![0.0];
3298        assert_eq!(sin(&x).unwrap().item_exact::<f32>(), 0.0);
3299
3300        let x = array![std::f32::consts::PI / 2.0];
3301        assert_float_eq! {
3302            sin(&x).unwrap().item_exact::<f32>(),
3303            (std::f32::consts::PI / 2.0f32).sin(),
3304            abs <= 1e-5
3305        };
3306
3307        assert_array_eq(
3308            sin(array!()).unwrap(),
3309            array!(),
3310            tolerances::EXACT.rtol,
3311            tolerances::EXACT.atol,
3312        );
3313
3314        // Integer input type
3315        let x = array![0];
3316        assert_eq!(x.dtype(), Dtype::Int32);
3317        assert_float_eq! {
3318            sin(&x).unwrap().item_exact::<f32>(),
3319            0.0f32.sin(),
3320            abs <= 1e-5
3321        };
3322
3323        // Input is irregularly strided
3324        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3325        let res = sin(&x).unwrap();
3326        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.sin())).unwrap();
3327        assert!(all_close(&res, &expected, None, None, None).unwrap());
3328
3329        let data = Array::from_slice(&[0.0, 1.0, 2.0, 3.0], &[2, 2]);
3330        let x = split_equal(&data, 2, 1).unwrap();
3331        let expected = Array::from_slice(&[0.0f32.sin(), 2.0f32.sin()], &[2, 1]);
3332        assert!(all_close(sin(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3333    }
3334
3335    #[test]
3336    fn test_unary_cos() {
3337        let x = array![0.0];
3338        assert_float_eq! {
3339            cos(&x).unwrap().item_exact::<f32>(),
3340            0.0f32.cos(),
3341            abs <= 1e-5
3342        };
3343
3344        let x = array![std::f32::consts::PI / 2.0];
3345        assert_float_eq! {
3346            cos(&x).unwrap().item_exact::<f32>(),
3347            (std::f32::consts::PI / 2.0f32).cos(),
3348            abs <= 1e-5
3349        };
3350
3351        assert_array_eq(
3352            cos(array!()).unwrap(),
3353            array!(),
3354            tolerances::EXACT.rtol,
3355            tolerances::EXACT.atol,
3356        );
3357
3358        // Integer input type
3359        let x = array![0];
3360        assert_eq!(x.dtype(), Dtype::Int32);
3361        assert_float_eq! {
3362            cos(&x).unwrap().item_exact::<f32>(),
3363            0.0f32.cos(),
3364            abs <= 1e-5
3365        };
3366
3367        // Input is irregularly strided
3368        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3369        let res = cos(&x).unwrap();
3370        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.cos())).unwrap();
3371        assert!(all_close(&res, &expected, None, None, None).unwrap());
3372
3373        let data = Array::from_slice(&[0.0, 1.0, 2.0, 3.0], &[2, 2]);
3374        let x = split_equal(&data, 2, 1).unwrap();
3375        let expected = Array::from_slice(&[0.0f32.cos(), 2.0f32.cos()], &[2, 1]);
3376        assert!(all_close(cos(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3377    }
3378
3379    #[test]
3380    fn test_unary_degrees() {
3381        let x = array![0.0];
3382        assert_eq!(degrees(&x).unwrap().item_exact::<f32>(), 0.0);
3383
3384        let x = array![std::f32::consts::PI / 2.0];
3385        assert_eq!(degrees(&x).unwrap().item_exact::<f32>(), 90.0);
3386
3387        assert_array_eq(
3388            degrees(array!()).unwrap(),
3389            array!(),
3390            tolerances::EXACT.rtol,
3391            tolerances::EXACT.atol,
3392        );
3393
3394        // Integer input type
3395        let x = array![0];
3396        assert_eq!(x.dtype(), Dtype::Int32);
3397        assert_eq!(degrees(&x).unwrap().item_exact::<f32>(), 0.0);
3398
3399        // Input is irregularly strided
3400        let x = broadcast_to(&array!(std::f32::consts::PI / 2.0), &[2, 2, 2]).unwrap();
3401        let res = degrees(&x).unwrap();
3402        let expected = Array::full::<f32>(&[2, 2, 2], array!(90.0)).unwrap();
3403        assert!(all_close(&res, &expected, None, None, None).unwrap());
3404
3405        let angles = Array::from_slice(&[0.0, PI / 2.0, PI, 1.5 * PI], &[2, 2]);
3406        let x = split_equal(&angles, 2, 1).unwrap();
3407        let expected = Array::from_slice(&[0.0, 180.0], &[2, 1]);
3408        assert!(all_close(degrees(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3409    }
3410
3411    #[test]
3412    fn test_unary_radians() {
3413        let x = array![0.0];
3414        assert_eq!(radians(&x).unwrap().item_exact::<f32>(), 0.0);
3415
3416        let x = array![90.0];
3417        assert_eq!(
3418            radians(&x).unwrap().item_exact::<f32>(),
3419            std::f32::consts::PI / 2.0
3420        );
3421
3422        assert_array_eq(
3423            radians(array!()).unwrap(),
3424            array!(),
3425            tolerances::EXACT.rtol,
3426            tolerances::EXACT.atol,
3427        );
3428
3429        // Integer input type
3430        let x = array![90];
3431        assert_eq!(x.dtype(), Dtype::Int32);
3432        assert_eq!(
3433            radians(&x).unwrap().item_exact::<f32>(),
3434            std::f32::consts::PI / 2.0
3435        );
3436
3437        // Input is irregularly strided
3438        let x = broadcast_to(&array!(90.0), &[2, 2, 2]).unwrap();
3439        let res = radians(&x).unwrap();
3440        let expected = Array::full::<f32>(&[2, 2, 2], array!(std::f32::consts::PI / 2.0)).unwrap();
3441        assert!(all_close(&res, &expected, None, None, None).unwrap());
3442
3443        let angles = Array::from_slice(&[0.0, 90.0, 180.0, 270.0], &[2, 2]);
3444        let x = split_equal(&angles, 2, 1).unwrap();
3445        let expected = Array::from_slice(&[0.0, PI], &[2, 1]);
3446        assert!(all_close(radians(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3447    }
3448
3449    #[test]
3450    fn test_unary_log() {
3451        let x = array![0.0];
3452        assert_eq!(log(&x).unwrap().item_exact::<f32>(), NEG_INF);
3453
3454        let x = array![1.0];
3455        assert_eq!(log(&x).unwrap().item_exact::<f32>(), 0.0);
3456
3457        // Integer input type
3458        let x = array![1];
3459        assert_eq!(log(&x).unwrap().dtype(), Dtype::Float32);
3460        assert_eq!(log(&x).unwrap().item_exact::<f32>(), 0.0);
3461
3462        // Input is irregularly strided
3463        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3464        let res = log(&x).unwrap();
3465        let expected = Array::full::<f32>(&[2, 2, 2], array!(0.0)).unwrap();
3466        assert!(all_close(&res, &expected, None, None, None).unwrap());
3467
3468        let data = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3469        let x = split_equal(&data, 2, 1).unwrap();
3470        let expected = Array::from_slice(&[1.0f32.ln(), 3.0f32.ln()], &[2, 1]);
3471        assert!(all_close(log(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3472    }
3473
3474    #[test]
3475    fn test_unary_log2() {
3476        let x = array![0.0];
3477        assert_eq!(log2(&x).unwrap().item_exact::<f32>(), NEG_INF);
3478
3479        let x = array![1.0];
3480        assert_eq!(log2(&x).unwrap().item_exact::<f32>(), 0.0);
3481
3482        let x = array![1024.0];
3483        assert_eq!(log2(&x).unwrap().item_exact::<f32>(), 10.0);
3484    }
3485
3486    #[test]
3487    fn test_unary_log10() {
3488        let x = array![0.0];
3489        assert_eq!(log10(&x).unwrap().item_exact::<f32>(), NEG_INF);
3490
3491        let x = array![1.0];
3492        assert_eq!(log10(&x).unwrap().item_exact::<f32>(), 0.0);
3493
3494        let x = array![1000.0];
3495        assert_eq!(log10(&x).unwrap().item_exact::<f32>(), 3.0);
3496    }
3497
3498    #[test]
3499    fn test_unary_log1p() {
3500        let x = array![-1.0];
3501        assert_float_eq! {
3502            log1p(&x).unwrap().item_exact::<f32>(),
3503            (-1.0f32).ln_1p(),
3504            abs <= 1e-5
3505        };
3506
3507        let x = array![1.0];
3508        assert_float_eq! {
3509            log1p(&x).unwrap().item_exact::<f32>(),
3510            1.0f32.ln_1p(),
3511            abs <= 1e-5
3512        };
3513
3514        // Integer input type
3515        let x = array![1];
3516        assert_eq!(log1p(&x).unwrap().dtype(), Dtype::Float32);
3517        assert_float_eq! {
3518            log1p(&x).unwrap().item_exact::<f32>(),
3519            1.0f32.ln_1p(),
3520            abs <= 1e-5
3521        };
3522
3523        // Input is irregularly strided
3524        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3525        let res = log1p(&x).unwrap();
3526        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.ln_1p())).unwrap();
3527        assert!(all_close(&res, &expected, None, None, None).unwrap());
3528
3529        let data = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3530        let x = split_equal(&data, 2, 1).unwrap();
3531        let expected = Array::from_slice(&[1.0f32.ln_1p(), 3.0f32.ln_1p()], &[2, 1]);
3532        assert!(all_close(log1p(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3533    }
3534
3535    #[test]
3536    fn test_unary_sigmoid() {
3537        let x = array![0.0];
3538        assert_float_eq! {
3539            sigmoid(&x).unwrap().item_exact::<f32>(),
3540            0.5,
3541            abs <= 1e-5
3542        };
3543
3544        // Integer input type
3545        let x = array![0];
3546        assert_eq!(sigmoid(&x).unwrap().dtype(), Dtype::Float32);
3547        assert_float_eq! {
3548            sigmoid(&x).unwrap().item_exact::<f32>(),
3549            0.5,
3550            abs <= 1e-5
3551        };
3552
3553        let inf = f32::INFINITY;
3554        let x = array![inf];
3555        assert_eq!(sigmoid(&x).unwrap().item_exact::<f32>(), 1.0);
3556
3557        let x = array![-inf];
3558        assert_eq!(sigmoid(&x).unwrap().item_exact::<f32>(), 0.0);
3559    }
3560
3561    #[test]
3562    fn test_unary_square() {
3563        let x = array![3.0];
3564        assert_eq!(square(&x).unwrap().item_exact::<f32>(), 9.0);
3565
3566        let x = array![2];
3567        assert_eq!(square(&x).unwrap().item_exact::<i32>(), 4);
3568
3569        let x = Array::full::<f32>(&[3, 3], array!(2.0)).unwrap();
3570        assert!(all_close(
3571            square(&x).unwrap(),
3572            Array::full::<f32>(&[3, 3], array!(4.0)).unwrap(),
3573            None,
3574            None,
3575            None
3576        )
3577        .unwrap());
3578    }
3579
3580    #[test]
3581    fn test_unary_sqrt_rsqrt() {
3582        let x = array![4.0];
3583        assert_eq!(sqrt(&x).unwrap().item_exact::<f32>(), 2.0);
3584        assert_eq!(rsqrt(&x).unwrap().item_exact::<f32>(), 0.5);
3585
3586        let x = Array::full::<f32>(&[3, 3], array!(9.0)).unwrap();
3587        assert!(all_close(
3588            sqrt(&x).unwrap(),
3589            Array::full::<f32>(&[3, 3], array!(3.0)).unwrap(),
3590            None,
3591            None,
3592            None
3593        )
3594        .unwrap());
3595
3596        let x = array![4i32];
3597        assert_eq!(sqrt(&x).unwrap().item_exact::<f32>(), 2.0);
3598        assert_eq!(rsqrt(&x).unwrap().item_exact::<f32>(), 0.5);
3599    }
3600
3601    #[test]
3602    fn test_unary_reciprocal() {
3603        let x = array![8.0];
3604        assert_eq!(reciprocal(&x).unwrap().item_exact::<f32>(), 0.125);
3605
3606        let x = array![2];
3607        let out = reciprocal(&x).unwrap();
3608        assert_eq!(out.dtype(), Dtype::Float32);
3609        assert_eq!(out.item_exact::<f32>(), 0.5);
3610
3611        let x = Array::full::<f32>(&[3, 3], array!(2.0)).unwrap();
3612        assert!(all_close(
3613            reciprocal(&x).unwrap(),
3614            Array::full::<f32>(&[3, 3], array!(0.5)).unwrap(),
3615            None,
3616            None,
3617            None
3618        )
3619        .unwrap());
3620    }
3621
3622    #[test]
3623    fn test_unary_real_imag() {
3624        let x = Array::from_complex(complex64::new(0.0, 1.0));
3625        assert_array_eq(
3626            real(&x).unwrap(),
3627            Array::from_f32(0.0),
3628            tolerances::EXACT.rtol,
3629            tolerances::EXACT.atol,
3630        );
3631        assert_array_eq(
3632            imag(&x).unwrap(),
3633            Array::from_f32(1.0),
3634            tolerances::EXACT.rtol,
3635            tolerances::EXACT.atol,
3636        );
3637    }
3638
3639    #[test]
3640    fn test_binary_add() {
3641        let x = array![1.0];
3642        let y = array![1.0];
3643        let z = add(&x, &y).unwrap();
3644        assert_eq!(z.item_exact::<f32>(), 2.0);
3645
3646        let z = &x + y;
3647        assert_eq!(z.item_exact::<f32>(), 2.0);
3648
3649        let z = add(z, &x).unwrap();
3650        assert_eq!(z.item_exact::<f32>(), 3.0);
3651
3652        // Chain a few adds:
3653        let mut out = x.deep_clone();
3654        for _ in 0..10 {
3655            out = add(&out, &x).unwrap();
3656        }
3657        assert_eq!(out.item_exact::<f32>(), 11.0);
3658
3659        // Works for different shapes
3660        let x = array!([1.0, 2.0, 3.0]);
3661        let y = array!([1.0, 2.0, 3.0]);
3662        let z = add(&x, &y).unwrap();
3663        assert_eq!(z.shape(), &[3]);
3664        assert_array_eq(
3665            z,
3666            array!([2.0, 4.0, 6.0]),
3667            tolerances::EXACT.rtol,
3668            tolerances::EXACT.atol,
3669        );
3670
3671        // Works with scalars
3672        let x = array!([1.0, 2.0, 3.0]);
3673        let y = &x + 2.0;
3674        assert_eq!(y.dtype(), Dtype::Float32);
3675        assert_array_eq(
3676            y,
3677            array!([3.0, 4.0, 5.0]),
3678            tolerances::EXACT.rtol,
3679            tolerances::EXACT.atol,
3680        );
3681        let y = &x + 2.0;
3682        assert_eq!(y.dtype(), Dtype::Float32);
3683        assert_array_eq(
3684            y,
3685            array!([3.0, 4.0, 5.0]),
3686            tolerances::EXACT.rtol,
3687            tolerances::EXACT.atol,
3688        );
3689
3690        // Check type promotion
3691        let y = x + 2;
3692        assert_eq!(y.dtype(), Dtype::Float32);
3693
3694        let y = array!([1, 2, 3]) + 2.0;
3695        assert_eq!(y.dtype(), Dtype::Float32);
3696        // assert!(array_equal(&y, &array![3.0, 4.0, 5.0]).item_exact::<bool>());
3697        assert_array_eq(
3698            y,
3699            array!([3.0, 4.0, 5.0]),
3700            tolerances::EXACT.rtol,
3701            tolerances::EXACT.atol,
3702        );
3703
3704        // Broadcasting works
3705        let x = broadcast_to(&array!(1.0), &[10]).unwrap();
3706        let y = broadcast_to(&array!(2.0), &[10]).unwrap();
3707        let z = add(&x, &y).unwrap();
3708        assert_array_eq(
3709            z,
3710            full::<f32>(&[10], array!(3.0)).unwrap(),
3711            tolerances::EXACT.rtol,
3712            tolerances::EXACT.atol,
3713        );
3714
3715        let x = Array::from_slice(&[1.0, 2.0], &[1, 2]);
3716        let y = Array::from_slice(&[1.0, 2.0], &[2, 1]);
3717        let z = add(&x, &y).unwrap();
3718        assert_eq!(z.shape(), &[2, 2]);
3719        assert_array_eq(
3720            z,
3721            Array::from_slice(&[2.0, 3.0, 3.0, 4.0], &[2, 2]),
3722            tolerances::EXACT.rtol,
3723            tolerances::EXACT.atol,
3724        );
3725
3726        let x = ones::<f32>(&[3, 2, 1]).unwrap();
3727        let z = x + 2.0;
3728        assert_eq!(z.shape(), &[3, 2, 1]);
3729        let expected = Array::from_slice(&[3.0, 3.0, 3.0, 3.0, 3.0, 3.0], &[3, 2, 1]);
3730        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
3731
3732        // Works for empty arrays
3733        let x = array!();
3734        let y = array!();
3735        let z = x + y;
3736        z.eval().unwrap();
3737        assert_eq!(z.size(), 0);
3738        assert_eq!(z.shape(), &[0]);
3739    }
3740
3741    #[test]
3742    fn test_binary_sub() {
3743        let x = array!([3.0, 2.0, 1.0]);
3744        let y = array!([1.0, 1.0, 1.0]);
3745        assert_array_eq(
3746            x - y,
3747            array!([2.0, 1.0, 0.0]),
3748            tolerances::EXACT.rtol,
3749            tolerances::EXACT.atol,
3750        );
3751    }
3752
3753    #[test]
3754    fn test_binary_mul() {
3755        let x = array!([1.0, 2.0, 3.0]);
3756        let y = array!([2.0, 2.0, 2.0]);
3757        assert_array_eq(
3758            x * y,
3759            array!([2.0, 4.0, 6.0]),
3760            tolerances::EXACT.rtol,
3761            tolerances::EXACT.atol,
3762        );
3763    }
3764
3765    #[test]
3766    fn test_binary_div() {
3767        let x = array![1.0];
3768        let y = array![1.0];
3769        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3770
3771        let x = array![1.0];
3772        let y = array![0.5];
3773        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 2.0);
3774
3775        let x = array![1.0];
3776        let y = array![4.0];
3777        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 0.25);
3778
3779        let x = array![true];
3780        let y = array![true];
3781        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3782
3783        let x = array![false];
3784        let y = array![true];
3785        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 0.0);
3786
3787        let x = array![true];
3788        let y = array![false];
3789        assert!(divide(&x, &y).unwrap().item_exact::<f32>().is_infinite());
3790
3791        let x = array![false];
3792        let y = array![false];
3793        assert!(divide(&x, &y).unwrap().item_exact::<f32>().is_nan());
3794    }
3795
3796    #[test]
3797    fn test_binary_maximum_minimum() {
3798        let x = array![1.0];
3799        let y = array![0.0];
3800        assert_eq!(maximum(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3801        assert_eq!(minimum(&x, &y).unwrap().item_exact::<f32>(), 0.0);
3802
3803        let y = array![2.0];
3804        assert_eq!(maximum(&x, &y).unwrap().item_exact::<f32>(), 2.0);
3805        assert_eq!(minimum(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3806    }
3807
3808    #[test]
3809    fn test_binary_logaddexp() {
3810        let x = array![0.0];
3811        let y = array![0.0];
3812        assert_float_eq! {
3813            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3814            2.0f32.ln(),
3815            abs <= 1e-5
3816        };
3817
3818        let x = array!([0u32]);
3819        let y = array!([10000u32]);
3820        assert_eq!(logaddexp(&x, &y).unwrap().item_exact::<f32>(), 10000.0);
3821
3822        let x = array![f32::INFINITY];
3823        let y = array![3.0];
3824        assert_eq!(
3825            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3826            f32::INFINITY
3827        );
3828
3829        let x = array![f32::NEG_INFINITY];
3830        let y = array![3.0];
3831        assert_eq!(logaddexp(&x, &y).unwrap().item_exact::<f32>(), 3.0);
3832
3833        let x = array![f32::NEG_INFINITY];
3834        let y = array![f32::NEG_INFINITY];
3835        assert_eq!(
3836            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3837            f32::NEG_INFINITY
3838        );
3839
3840        let x = array![f32::INFINITY];
3841        let y = array![f32::INFINITY];
3842        assert_eq!(
3843            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3844            f32::INFINITY
3845        );
3846
3847        let x = array![f32::NEG_INFINITY];
3848        let y = array![f32::INFINITY];
3849        assert_eq!(
3850            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3851            f32::INFINITY
3852        );
3853    }
3854
3855    #[test]
3856    fn test_basic_clip() {
3857        let a = array!([1.0, 4.0, 3.0, 8.0, 5.0]);
3858        let expected = array!([2.0, 4.0, 3.0, 6.0, 5.0]);
3859        let clipped = clip(&a, (array!(2.0), array!(6.0))).unwrap();
3860        assert_array_eq(
3861            clipped,
3862            &expected,
3863            tolerances::EXACT.rtol,
3864            tolerances::EXACT.atol,
3865        );
3866
3867        // Test with scalar
3868        let clipped = clip(&a, (2.0, 6.0)).unwrap();
3869        assert_array_eq(
3870            clipped,
3871            &expected,
3872            tolerances::EXACT.rtol,
3873            tolerances::EXACT.atol,
3874        );
3875    }
3876
3877    #[test]
3878    fn test_clip_with_only_min() {
3879        let a = array!([-1.0, 1.0, 0.0, 5.0]);
3880        let expected = array!([0.0, 1.0, 0.0, 5.0]);
3881        let clipped = clip(&a, (array!(0.0), ())).unwrap();
3882        assert_array_eq(
3883            clipped,
3884            &expected,
3885            tolerances::EXACT.rtol,
3886            tolerances::EXACT.atol,
3887        );
3888
3889        // Test with scalar
3890        let clipped = clip(&a, (0.0, ())).unwrap();
3891        assert_array_eq(
3892            clipped,
3893            expected,
3894            tolerances::EXACT.rtol,
3895            tolerances::EXACT.atol,
3896        );
3897    }
3898
3899    #[test]
3900    fn test_clip_with_only_max() {
3901        let a = array!([2.0, 3.0, 4.0, 5.0]);
3902        let expected = array!([2.0, 3.0, 4.0, 4.0]);
3903        let clipped = clip(&a, ((), array!(4.0))).unwrap();
3904        assert_array_eq(
3905            clipped,
3906            &expected,
3907            tolerances::EXACT.rtol,
3908            tolerances::EXACT.atol,
3909        );
3910
3911        // Test with scalar
3912        let clipped = clip(&a, ((), 4.0)).unwrap();
3913        assert_array_eq(
3914            clipped,
3915            expected,
3916            tolerances::EXACT.rtol,
3917            tolerances::EXACT.atol,
3918        );
3919    }
3920
3921    #[test]
3922    fn test_tensordot() {
3923        let x = reshape(arange::<_, f32>(None, 60.0, None).unwrap(), &[3, 4, 5]).unwrap();
3924        let y = reshape(arange::<_, f32>(None, 24.0, None).unwrap(), &[4, 3, 2]).unwrap();
3925        let z = tensordot_axes(&x, &y, &[1i32, 0], &[0i32, 1]).unwrap();
3926        let expected = Array::from_slice(
3927            &[
3928                4400.0_f32, 4730.0, 4532.0, 4874.0, 4664.0, 5018.0, 4796.0, 5162.0, 4928.0, 5306.0,
3929            ],
3930            &[5, 2],
3931        );
3932        assert_array_eq_with_context(
3933            z,
3934            expected,
3935            tolerances::EXACT.rtol,
3936            tolerances::EXACT.atol,
3937            "float32 explicit-axis contraction",
3938        );
3939
3940        let x = reshape(arange::<_, f32>(None, 360.0, None).unwrap(), &[3, 4, 5, 6]).unwrap();
3941        let y = reshape(arange::<_, f32>(None, 360.0, None).unwrap(), &[6, 4, 5, 3]).unwrap();
3942        assert!(tensordot_axes(&x, &y, &[2, 1, 3], &[1, 2, 0]).is_err());
3943
3944        let x = reshape(arange::<_, f32>(None, 60.0, None).unwrap(), &[3, 4, 5]).unwrap();
3945        let y = reshape(arange::<_, f32>(None, 120.0, None).unwrap(), &[4, 5, 6]).unwrap();
3946
3947        let z = tensordot_axis(&x, &y, 2).unwrap();
3948        let expected = Array::from_slice(
3949            &[
3950                14820.0, 15010.0, 15200.0, 15390.0, 15580.0, 15770.0, 37620.0, 38210.0, 38800.0,
3951                39390.0, 39980.0, 40570.0, 60420.0, 61410.0, 62400.0, 63390.0, 64380.0, 65370.0,
3952            ],
3953            &[3, 6],
3954        );
3955        assert_array_eq_with_context(
3956            z,
3957            expected,
3958            tolerances::EXACT.rtol,
3959            tolerances::EXACT.atol,
3960            "float32 axis-count contraction",
3961        );
3962    }
3963
3964    #[test]
3965    fn test_outer() {
3966        let x = arange::<_, f32>(1.0, 5.0, None).unwrap();
3967        let y = arange::<_, f32>(1.0, 4.0, None).unwrap();
3968        let z = outer(&x, &y).unwrap();
3969        let expected = Array::from_slice(
3970            &[1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0, 4.0, 8.0, 12.0],
3971            &[4, 3],
3972        );
3973        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
3974
3975        let x = ones::<f32>(&[5]).unwrap();
3976        let y = linspace::<_, f32>(
3977            -2.0,
3978            2.0,
3979            crate::ops::LinspaceOptions {
3980                count: 5,
3981                endpoint: true,
3982            },
3983        )
3984        .unwrap();
3985        let z = outer(&x, &y).unwrap();
3986        let expected = Array::from_slice(
3987            &[
3988                -2.0, -1.0, 0.0, 1.0, 2.0, -2.0, -1.0, 0.0, 1.0, 2.0, -2.0, -1.0, 0.0, 1.0, 2.0,
3989                -2.0, -1.0, 0.0, 1.0, 2.0, -2.0, -1.0, 0.0, 1.0, 2.0,
3990            ],
3991            &[5, 5],
3992        );
3993        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
3994    }
3995
3996    #[test]
3997    fn test_inner() {
3998        let x = reshape(arange::<_, f32>(None, 5.0, None).unwrap(), &[1, 5]).unwrap();
3999        let y = reshape(arange::<_, f32>(None, 6.0, None).unwrap(), &[2, 3]).unwrap();
4000        assert!(inner(&x, &y).is_err());
4001
4002        let x = array!([1.0, 2.0, 3.0]);
4003        let y = array!([0.0, 1.0, 0.0]);
4004        let z = inner(&x, &y).unwrap();
4005        assert_eq!(z.item_exact::<f32>(), 2.0);
4006
4007        let x = reshape(arange::<_, f32>(None, 24.0, None).unwrap(), &[2, 3, 4]).unwrap();
4008        let y = arange::<_, f32>(None, 4.0, None).unwrap();
4009        let z = inner(&x, &y).unwrap();
4010        let expected = Array::from_slice(&[14.0, 38.0, 62.0, 86.0, 110.0, 134.0], &[2, 3]);
4011        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4012
4013        let x = reshape(arange::<_, f32>(None, 2.0, None).unwrap(), &[1, 1, 2]).unwrap();
4014        let y = reshape(arange::<_, f32>(None, 6.0, None).unwrap(), &[3, 2]).unwrap();
4015        let z = inner(&x, &y).unwrap();
4016        let expected = Array::from_slice(&[1.0, 3.0, 5.0], &[1, 1, 3]);
4017        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4018
4019        let x = eye::<f32>(2, None, None).unwrap();
4020        let y = Array::from_f32(7.0);
4021        let z = inner(&x, &y).unwrap();
4022        let expected = Array::from_slice(&[7.0, 0.0, 0.0, 7.0], &[2, 2]);
4023        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4024    }
4025
4026    #[test]
4027    fn test_divmod() {
4028        let x = array!([1.0, 2.0, 3.0]);
4029        let y = array!([1.0, 1.0, 1.0]);
4030        let out = divmod(&x, &y).unwrap();
4031        assert_array_eq(
4032            out.0,
4033            array!([1.0, 2.0, 3.0]),
4034            tolerances::EXACT.rtol,
4035            tolerances::EXACT.atol,
4036        );
4037        assert_array_eq(
4038            out.1,
4039            array!([0.0, 0.0, 0.0]),
4040            tolerances::EXACT.rtol,
4041            tolerances::EXACT.atol,
4042        );
4043
4044        let x = array!([5.0, 6.0, 7.0]);
4045        let y = array!([2.0, 2.0, 2.0]);
4046        let out = divmod(&x, &y).unwrap();
4047        assert_array_eq(
4048            out.0,
4049            array!([2.0, 3.0, 3.0]),
4050            tolerances::EXACT.rtol,
4051            tolerances::EXACT.atol,
4052        );
4053        assert_array_eq(
4054            out.1,
4055            array!([1.0, 0.0, 1.0]),
4056            tolerances::EXACT.rtol,
4057            tolerances::EXACT.atol,
4058        );
4059
4060        let x = array!([5.0, 6.0, 7.0]);
4061        let y = array!([2.0, 2.0, 2.0]);
4062        let out = divmod(&x, &y).unwrap();
4063        assert_array_eq(
4064            out.0,
4065            array!([2.0, 3.0, 3.0]),
4066            tolerances::EXACT.rtol,
4067            tolerances::EXACT.atol,
4068        );
4069        assert_array_eq(
4070            out.1,
4071            array!([1.0, 0.0, 1.0]),
4072            tolerances::EXACT.rtol,
4073            tolerances::EXACT.atol,
4074        );
4075
4076        let x = array![complex64::new(1.0, 0.0)];
4077        let y = array![complex64::new(2.0, 0.0)];
4078        assert!(divmod(&x, &y).is_err());
4079
4080        // Check that we can eval on both outputs
4081        let x = array![1.0];
4082        let y = array![2.0];
4083        let (quo, rem) = divmod(&x, &y).unwrap();
4084        eval([&quo, &rem]).unwrap();
4085        assert_eq!(quo.item_exact::<f32>(), 0.0);
4086        assert_eq!(rem.item_exact::<f32>(), 1.0);
4087
4088        // Check nested in the graph
4089        let x = array![1.0];
4090        let y = array![2.0];
4091        let (quo, rem) = divmod(&x, &y).unwrap();
4092        let z = quo + rem;
4093        assert_eq!(z.item_exact::<f32>(), 1.0);
4094
4095        // Check that we can still eval when one output goes out of scope
4096        let mut out_holder = {
4097            let (quo, _) = divmod(&x, &y).unwrap();
4098            vec![quo]
4099        };
4100        eval(out_holder.iter()).unwrap();
4101        assert_eq!(out_holder[0].item_exact::<f32>(), 0.0);
4102
4103        // Check that we can still eval when the other output goes out of scope
4104        out_holder.clear();
4105        let out_holder = {
4106            let (_, rem) = divmod(&x, &y).unwrap();
4107            vec![rem]
4108        };
4109        eval(out_holder.iter()).unwrap();
4110        assert_eq!(out_holder[0].item_exact::<f32>(), 1.0);
4111    }
4112
4113    // The tests below are adapted from the python unit test `test_blas.py/test_segmented_mm`
4114    #[test]
4115    fn test_segmented_mm() {
4116        use crate::ops::{indexing::*, stack};
4117        use crate::random;
4118
4119        // Reference implementation: for each segment [s1, s2], compute a[:, s1:s2] @ b[s1:s2, :]
4120        fn segmented_mm_ref(a: &Array, b: &Array, segments: &Array) -> Array {
4121            let segments_data: Vec<Vec<u32>> = (0..segments.shape()[0])
4122                .map(|i| {
4123                    let row = segments.index(i);
4124                    vec![
4125                        row.index(0).item_exact::<u32>(),
4126                        row.index(1).item_exact::<u32>(),
4127                    ]
4128                })
4129                .collect();
4130
4131            let results: Vec<Array> = segments_data
4132                .iter()
4133                .map(|seg| {
4134                    let s1 = seg[0] as i32;
4135                    let s2 = seg[1] as i32;
4136                    let a_slice = a.index((.., s1..s2));
4137                    let b_slice = b.index((s1..s2, ..));
4138                    a_slice.matmul(&b_slice).unwrap()
4139                })
4140                .collect();
4141
4142            stack(&results, 0).unwrap()
4143        }
4144
4145        // Test shapes from Python test
4146        let shapes = [(10, 10, 10), (10, 10, 100), (100, 100, 100)];
4147
4148        // Segment patterns from Python test
4149        let all_segments: Vec<Vec<f32>> = vec![
4150            vec![0.0, 0.0, 1.0],
4151            vec![0.0, 0.5, 1.0],
4152            (0..10).map(|r| r as f32 / 9.0).collect(),
4153        ];
4154
4155        random::seed(42).unwrap();
4156
4157        for (m, n, k) in shapes {
4158            for s in &all_segments {
4159                // Build segments array from proportions
4160                let mut segments_vec: Vec<[u32; 2]> = Vec::new();
4161                for i in 0..s.len() - 1 {
4162                    let s1 = ((k as f32 * s[i]) as u32).min(k as u32 - 1);
4163                    let s2 = ((k as f32 * s[i + 1]) as u32).min(k as u32 - 1);
4164                    segments_vec.push([s1, s2]);
4165                }
4166                let segments_flat: Vec<u32> = segments_vec.iter().flat_map(|x| *x).collect();
4167                let segments = Array::from_slice(&segments_flat, &[segments_vec.len() as i32, 2]);
4168
4169                // Test a @ b
4170                let a = random::normal::<f32>(&[m, k], None, None, None).unwrap();
4171                let b = random::normal::<f32>(&[k, n], None, None, None).unwrap();
4172                let c1 = segmented_mm_ref(&a, &b, &segments);
4173                let c2 = segmented_mm(&a, &b, &segments).unwrap();
4174                assert!(
4175                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4176                    "segmented_mm failed for shape ({}, {}, {}) with segments {:?}",
4177                    m,
4178                    n,
4179                    k,
4180                    s
4181                );
4182
4183                // Test a.T @ b (transposed a)
4184                let a = random::normal::<f32>(&[k, m], None, None, None).unwrap();
4185                let b = random::normal::<f32>(&[k, n], None, None, None).unwrap();
4186                let a_t = a.t();
4187                let c1 = segmented_mm_ref(&a_t, &b, &segments);
4188                let c2 = segmented_mm(&a_t, &b, &segments).unwrap();
4189                assert!(
4190                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4191                    "segmented_mm with transposed a failed for shape ({}, {}, {})",
4192                    m,
4193                    n,
4194                    k
4195                );
4196
4197                // Test a @ b.T (transposed b)
4198                let a = random::normal::<f32>(&[m, k], None, None, None).unwrap();
4199                let b = random::normal::<f32>(&[n, k], None, None, None).unwrap();
4200                let b_t = b.t();
4201                let c1 = segmented_mm_ref(&a, &b_t, &segments);
4202                let c2 = segmented_mm(&a, &b_t, &segments).unwrap();
4203                assert!(
4204                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4205                    "segmented_mm with transposed b failed for shape ({}, {}, {})",
4206                    m,
4207                    n,
4208                    k
4209                );
4210
4211                // Test a.T @ b.T (both transposed)
4212                let a = random::normal::<f32>(&[k, m], None, None, None).unwrap();
4213                let b = random::normal::<f32>(&[n, k], None, None, None).unwrap();
4214                let a_t = a.t();
4215                let b_t = b.t();
4216                let c1 = segmented_mm_ref(&a_t, &b_t, &segments);
4217                let c2 = segmented_mm(&a_t, &b_t, &segments).unwrap();
4218                assert!(
4219                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4220                    "segmented_mm with both transposed failed for shape ({}, {}, {})",
4221                    m,
4222                    n,
4223                    k
4224                );
4225            }
4226        }
4227    }
4228
4229    #[test]
4230    fn test_segmented_mm_batched_error() {
4231        // Batched input should fail (matches Python test)
4232        let a = ones::<f32>(&[2, 10, 10]).unwrap();
4233        let segments = Array::from_slice(&[0u32, 5, 5, 10], &[2, 2]);
4234        let result = segmented_mm(&a, &a, &segments);
4235        assert!(
4236            result.is_err(),
4237            "segmented_mm should fail for batched input"
4238        );
4239    }
4240
4241    // Tests adapted from Python test `test_blas.py/test_gather_matmul`
4242    #[test]
4243    fn test_gather_mm() {
4244        use crate::ops::indexing::take_axis;
4245        use crate::random;
4246
4247        random::seed(0).unwrap();
4248
4249        // Reference implementation using take
4250        fn gather_mm_ref(
4251            a: &Array,
4252            b: &Array,
4253            lhs_indices: Option<&Array>,
4254            rhs_indices: Option<&Array>,
4255        ) -> Array {
4256            let a = a
4257                .reshape(&[-1, a.shape()[a.ndim() - 2], a.shape()[a.ndim() - 1]])
4258                .unwrap();
4259            let b = b
4260                .reshape(&[-1, b.shape()[b.ndim() - 2], b.shape()[b.ndim() - 1]])
4261                .unwrap();
4262
4263            let a_gathered = match lhs_indices {
4264                Some(idx) => take_axis(&a, idx, 0).unwrap(),
4265                None => a,
4266            };
4267            let b_gathered = match rhs_indices {
4268                Some(idx) => take_axis(&b, idx, 0).unwrap(),
4269                None => b,
4270            };
4271            a_gathered.matmul(&b_gathered).unwrap()
4272        }
4273
4274        // Test case 1: batch_A=(1,), lhs_indices=(0,), batch_B=(3,), rhs_indices=(2, 1)
4275        let a = random::normal::<f32>(&[1, 32, 32], None, None, None).unwrap();
4276        let b = random::normal::<f32>(&[3, 32, 32], None, None, None).unwrap();
4277        let lhs_indices = Array::from_slice(&[0u32], &[1]);
4278        let rhs_indices = Array::from_slice(&[2u32, 1], &[2]);
4279
4280        let out_ref = gather_mm_ref(&a, &b, Some(&lhs_indices), Some(&rhs_indices));
4281        let out_test = gather_mm(&a, &b, &lhs_indices, &rhs_indices, None).unwrap();
4282        assert!(
4283            out_ref.all_close(&out_test, 1e-5, 1e-5, None).unwrap(),
4284            "gather_mm test case 1 failed"
4285        );
4286
4287        // Test case 2: batch_A=(1,), lhs_indices=None, batch_B=(3,), rhs_indices=(2, 1)
4288        let out_ref = gather_mm_ref(&a, &b, None, Some(&rhs_indices));
4289        let out_test = gather_mm(&a, &b, None::<&Array>, &rhs_indices, None).unwrap();
4290        assert!(
4291            out_ref.all_close(&out_test, 1e-5, 1e-5, None).unwrap(),
4292            "gather_mm test case 2 failed"
4293        );
4294
4295        // Test case 3: batch_A=(5,), lhs_indices=(0, 2), batch_B=(3,), rhs_indices=(2, 1)
4296        let a = random::normal::<f32>(&[5, 32, 32], None, None, None).unwrap();
4297        let lhs_indices = Array::from_slice(&[0u32, 2], &[2]);
4298
4299        let out_ref = gather_mm_ref(&a, &b, Some(&lhs_indices), Some(&rhs_indices));
4300        let out_test = gather_mm(&a, &b, &lhs_indices, &rhs_indices, None).unwrap();
4301        assert!(
4302            out_ref.all_close(&out_test, 1e-5, 1e-5, None).unwrap(),
4303            "gather_mm test case 3 failed"
4304        );
4305    }
4306
4307    // Test adapted from Python test `test_blas.py/test_gather_mm_sorted`
4308    #[test]
4309    fn test_gather_mm_sorted() {
4310        use crate::ops::indexing::take_axis;
4311        use crate::ops::sort;
4312        use crate::random;
4313
4314        random::seed(0).unwrap();
4315
4316        // Reference implementation
4317        fn gather_mm_ref(a: &Array, b: &Array, rhs: &Array) -> Array {
4318            let b_gathered = take_axis(b, rhs, 0).unwrap();
4319            a.matmul(&b_gathered).unwrap()
4320        }
4321
4322        let a = random::normal::<f32>(&[100, 1, 100], None, None, None).unwrap();
4323        let b = random::normal::<f32>(&[8, 100, 100], None, None, None).unwrap();
4324        let rhs = sort(&random::randint::<_, i32>(0, 8, &[100], None).unwrap()).unwrap();
4325
4326        let c1 = gather_mm_ref(&a, &b, &rhs);
4327        let c2 = gather_mm(&a, &b, None::<&Array>, &rhs, true).unwrap();
4328        assert!(
4329            c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4330            "gather_mm_sorted failed"
4331        );
4332    }
4333}