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
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).unwrap();
1684/// assert_eq!(result.shape(), &[2, 10, 10]);
1685/// assert!(result.as_slice::<f32>().iter().all(|&value| value == 50.0));
1686/// ```
1687pub fn segmented_mm(
1688    a: impl AsRef<Array>,
1689    b: impl AsRef<Array>,
1690    segments: impl AsRef<Array>,
1691) -> Result<Array> {
1692    let stream = Stream::thread_local_or_default();
1693    Array::try_from_op(|res| unsafe {
1694        mlx_sys::mlx_segmented_mm(
1695            res,
1696            a.as_ref().as_ptr(),
1697            b.as_ref().as_ptr(),
1698            segments.as_ref().as_ptr(),
1699            stream.as_ref().as_ptr(),
1700        )
1701    })
1702}
1703
1704/// Compatibility shim for [`segmented_mm`].
1705#[generate_macro(customize(forwarding_shim = true))]
1706#[deprecated(
1707    since = "0.26.0",
1708    note = "use `with_stream` or `with_device` around `segmented_mm`"
1709)]
1710pub fn segmented_mm_device(
1711    a: impl AsRef<Array>,
1712    b: impl AsRef<Array>,
1713    segments: impl AsRef<Array>,
1714    #[optional] stream: impl AsRef<Stream>,
1715) -> Result<Array> {
1716    crate::with_stream(stream.as_ref(), || segmented_mm(a, b, segments))
1717}
1718
1719/// Element-wise maximum.
1720///
1721/// Take the element-wise max of two arrays with numpy-style broadcasting semantics. Either or both
1722/// input arrays can also be scalars.
1723pub fn maximum(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1724    let stream = Stream::thread_local_or_default();
1725    let a_ptr = a.as_ref().as_ptr();
1726    let b_ptr = b.as_ref().as_ptr();
1727
1728    Array::try_from_op(|res| unsafe {
1729        mlx_sys::mlx_maximum(res, a_ptr, b_ptr, stream.as_ref().as_ptr())
1730    })
1731}
1732
1733/// Compatibility shim for [`maximum`].
1734#[generate_macro(customize(forwarding_shim = true))]
1735#[deprecated(
1736    since = "0.26.0",
1737    note = "use `with_stream` or `with_device` around `maximum`"
1738)]
1739pub fn maximum_device(
1740    a: impl AsRef<Array>,
1741    b: impl AsRef<Array>,
1742    #[optional] stream: impl AsRef<Stream>,
1743) -> Result<Array> {
1744    crate::with_stream(stream.as_ref(), || maximum(a, b))
1745}
1746
1747/// Element-wise minimum.
1748///
1749/// Take the element-wise min of two arrays with numpy-style broadcasting semantics. Either or both
1750/// input arrays can also be scalars.
1751pub fn minimum(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1752    let stream = Stream::thread_local_or_default();
1753    let a_ptr = a.as_ref().as_ptr();
1754    let b_ptr = b.as_ref().as_ptr();
1755
1756    Array::try_from_op(|res| unsafe {
1757        mlx_sys::mlx_minimum(res, a_ptr, b_ptr, stream.as_ref().as_ptr())
1758    })
1759}
1760
1761/// Compatibility shim for [`minimum`].
1762#[generate_macro(customize(forwarding_shim = true))]
1763#[deprecated(
1764    since = "0.26.0",
1765    note = "use `with_stream` or `with_device` around `minimum`"
1766)]
1767pub fn minimum_device(
1768    a: impl AsRef<Array>,
1769    b: impl AsRef<Array>,
1770    #[optional] stream: impl AsRef<Stream>,
1771) -> Result<Array> {
1772    crate::with_stream(stream.as_ref(), || minimum(a, b))
1773}
1774
1775/// See [`Array::multiply`].
1776pub fn multiply(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1777    a.as_ref().multiply(b)
1778}
1779
1780/// Compatibility shim for [`multiply`].
1781#[generate_macro(customize(forwarding_shim = true))]
1782#[deprecated(
1783    since = "0.26.0",
1784    note = "use `with_stream` or `with_device` around `multiply`"
1785)]
1786pub fn multiply_device(
1787    a: impl AsRef<Array>,
1788    b: impl AsRef<Array>,
1789    #[optional] stream: impl AsRef<Stream>,
1790) -> Result<Array> {
1791    crate::with_stream(stream.as_ref(), || multiply(a, b))
1792}
1793
1794/// See [`Array::negative`].
1795pub fn negative(a: impl AsRef<Array>) -> Result<Array> {
1796    a.as_ref().negative()
1797}
1798
1799/// Compatibility shim for [`negative`].
1800#[generate_macro(customize(forwarding_shim = true))]
1801#[deprecated(
1802    since = "0.26.0",
1803    note = "use `with_stream` or `with_device` around `negative`"
1804)]
1805pub fn negative_device(
1806    a: impl AsRef<Array>,
1807    #[optional] stream: impl AsRef<Stream>,
1808) -> Result<Array> {
1809    crate::with_stream(stream.as_ref(), || negative(a))
1810}
1811
1812/// See [`Array::power`].
1813pub fn power(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1814    a.as_ref().power(b)
1815}
1816
1817/// Compatibility shim for [`power`].
1818#[generate_macro(customize(forwarding_shim = true))]
1819#[deprecated(
1820    since = "0.26.0",
1821    note = "use `with_stream` or `with_device` around `power`"
1822)]
1823pub fn power_device(
1824    a: impl AsRef<Array>,
1825    b: impl AsRef<Array>,
1826    #[optional] stream: impl AsRef<Stream>,
1827) -> Result<Array> {
1828    crate::with_stream(stream.as_ref(), || power(a, b))
1829}
1830
1831/// Convert angles from degrees to radians.
1832pub fn radians(a: impl AsRef<Array>) -> Result<Array> {
1833    let stream = Stream::thread_local_or_default();
1834    Array::try_from_op(|res| unsafe {
1835        mlx_sys::mlx_radians(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1836    })
1837}
1838
1839/// Compatibility shim for [`radians`].
1840#[generate_macro(customize(forwarding_shim = true))]
1841#[deprecated(
1842    since = "0.26.0",
1843    note = "use `with_stream` or `with_device` around `radians`"
1844)]
1845pub fn radians_device(
1846    a: impl AsRef<Array>,
1847    #[optional] stream: impl AsRef<Stream>,
1848) -> Result<Array> {
1849    crate::with_stream(stream.as_ref(), || radians(a))
1850}
1851
1852/// See [`Array::reciprocal`].
1853pub fn reciprocal(a: impl AsRef<Array>) -> Result<Array> {
1854    a.as_ref().reciprocal()
1855}
1856
1857/// Compatibility shim for [`reciprocal`].
1858#[generate_macro(customize(forwarding_shim = true))]
1859#[deprecated(
1860    since = "0.26.0",
1861    note = "use `with_stream` or `with_device` around `reciprocal`"
1862)]
1863pub fn reciprocal_device(
1864    a: impl AsRef<Array>,
1865    #[optional] stream: impl AsRef<Stream>,
1866) -> Result<Array> {
1867    crate::with_stream(stream.as_ref(), || reciprocal(a))
1868}
1869
1870/// See [`Array::remainder`].
1871pub fn remainder(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
1872    a.as_ref().remainder(b)
1873}
1874
1875/// Compatibility shim for [`remainder`].
1876#[generate_macro(customize(forwarding_shim = true))]
1877#[deprecated(
1878    since = "0.26.0",
1879    note = "use `with_stream` or `with_device` around `remainder`"
1880)]
1881pub fn remainder_device(
1882    a: impl AsRef<Array>,
1883    b: impl AsRef<Array>,
1884    #[optional] stream: impl AsRef<Stream>,
1885) -> Result<Array> {
1886    crate::with_stream(stream.as_ref(), || remainder(a, b))
1887}
1888
1889/// See [`Array::round`].
1890pub fn round(a: impl AsRef<Array>, decimals: impl Into<Option<i32>>) -> Result<Array> {
1891    a.as_ref().round(decimals)
1892}
1893
1894/// Compatibility shim for [`round`].
1895#[generate_macro(customize(forwarding_shim = true))]
1896#[deprecated(
1897    since = "0.26.0",
1898    note = "use `with_stream` or `with_device` around `round`"
1899)]
1900pub fn round_device(
1901    a: impl AsRef<Array>,
1902    decimals: impl Into<Option<i32>>,
1903    #[optional] stream: impl AsRef<Stream>,
1904) -> Result<Array> {
1905    crate::with_stream(stream.as_ref(), || round(a, decimals))
1906}
1907
1908/// See [`Array::rsqrt`].
1909pub fn rsqrt(a: impl AsRef<Array>) -> Result<Array> {
1910    a.as_ref().rsqrt()
1911}
1912
1913/// Compatibility shim for [`rsqrt`].
1914#[generate_macro(customize(forwarding_shim = true))]
1915#[deprecated(
1916    since = "0.26.0",
1917    note = "use `with_stream` or `with_device` around `rsqrt`"
1918)]
1919pub fn rsqrt_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1920    crate::with_stream(stream.as_ref(), || rsqrt(a))
1921}
1922
1923/// Element-wise logistic sigmoid.
1924///
1925/// See the [python API
1926/// docs](https://ml-explore.github.io/mlx/build/html/python/_autosummary/mlx.core.sigmoid.html#mlx.core.sigmoid)
1927/// for more information
1928pub fn sigmoid(a: impl AsRef<Array>) -> Result<Array> {
1929    let stream = Stream::thread_local_or_default();
1930    Array::try_from_op(|res| unsafe {
1931        mlx_sys::mlx_sigmoid(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1932    })
1933}
1934
1935/// Compatibility shim for [`sigmoid`].
1936#[generate_macro(customize(forwarding_shim = true))]
1937#[deprecated(
1938    since = "0.26.0",
1939    note = "use `with_stream` or `with_device` around `sigmoid`"
1940)]
1941pub fn sigmoid_device(
1942    a: impl AsRef<Array>,
1943    #[optional] stream: impl AsRef<Stream>,
1944) -> Result<Array> {
1945    crate::with_stream(stream.as_ref(), || sigmoid(a))
1946}
1947
1948/// Element-wise sign.
1949pub fn sign(a: impl AsRef<Array>) -> Result<Array> {
1950    let stream = Stream::thread_local_or_default();
1951    Array::try_from_op(|res| unsafe {
1952        mlx_sys::mlx_sign(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1953    })
1954}
1955
1956/// Compatibility shim for [`sign`].
1957#[generate_macro(customize(forwarding_shim = true))]
1958#[deprecated(
1959    since = "0.26.0",
1960    note = "use `with_stream` or `with_device` around `sign`"
1961)]
1962pub fn sign_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1963    crate::with_stream(stream.as_ref(), || sign(a))
1964}
1965
1966/// See [`Array::sin`].
1967pub fn sin(a: impl AsRef<Array>) -> Result<Array> {
1968    a.as_ref().sin()
1969}
1970
1971/// Compatibility shim for [`sin`].
1972#[generate_macro(customize(forwarding_shim = true))]
1973#[deprecated(
1974    since = "0.26.0",
1975    note = "use `with_stream` or `with_device` around `sin`"
1976)]
1977pub fn sin_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1978    crate::with_stream(stream.as_ref(), || sin(a))
1979}
1980
1981/// Element-wise hyperbolic sine.
1982pub fn sinh(a: impl AsRef<Array>) -> Result<Array> {
1983    let stream = Stream::thread_local_or_default();
1984    Array::try_from_op(|res| unsafe {
1985        mlx_sys::mlx_sinh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
1986    })
1987}
1988
1989/// Compatibility shim for [`sinh`].
1990#[generate_macro(customize(forwarding_shim = true))]
1991#[deprecated(
1992    since = "0.26.0",
1993    note = "use `with_stream` or `with_device` around `sinh`"
1994)]
1995pub fn sinh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
1996    crate::with_stream(stream.as_ref(), || sinh(a))
1997}
1998
1999/// Perform the softmax along the given axis.
2000///
2001/// See the [python API
2002/// docs](https://ml-explore.github.io/mlx/build/html/python/_autosummary/mlx.core.softmax.html#mlx.core.softmax)
2003/// for more information.
2004pub fn softmax_axes(
2005    a: impl AsRef<Array>,
2006    axes: &[i32],
2007    precise: impl Into<Option<bool>>,
2008) -> Result<Array> {
2009    let stream = Stream::thread_local_or_default();
2010    let precise = precise.into().unwrap_or(false);
2011    let s = stream.as_ref().as_ptr();
2012
2013    Array::try_from_op(|res| unsafe {
2014        mlx_sys::mlx_softmax_axes(
2015            res,
2016            a.as_ref().as_ptr(),
2017            axes.as_ptr(),
2018            axes.len(),
2019            precise,
2020            s,
2021        )
2022    })
2023}
2024
2025/// Compatibility shim for [`softmax_axes`].
2026#[generate_macro(customize(forwarding_shim = true))]
2027#[deprecated(
2028    since = "0.26.0",
2029    note = "use `with_stream` or `with_device` around `softmax_axes`"
2030)]
2031pub fn softmax_axes_device(
2032    a: impl AsRef<Array>,
2033    axes: &[i32],
2034    precise: impl Into<Option<bool>>,
2035    #[optional] stream: impl AsRef<Stream>,
2036) -> Result<Array> {
2037    crate::with_stream(stream.as_ref(), || softmax_axes(a, axes, precise))
2038}
2039
2040/// Similar to [`softmax_axes`] but with a single axis.
2041pub fn softmax_axis(
2042    a: impl AsRef<Array>,
2043    axis: i32,
2044    precise: impl Into<Option<bool>>,
2045) -> Result<Array> {
2046    let stream = Stream::thread_local_or_default();
2047    let precise = precise.into().unwrap_or(false);
2048    let s = stream.as_ref().as_ptr();
2049
2050    Array::try_from_op(|res| unsafe {
2051        mlx_sys::mlx_softmax_axis(res, a.as_ref().as_ptr(), axis, precise, s)
2052    })
2053}
2054
2055/// Compatibility shim for [`softmax_axis`].
2056#[generate_macro(customize(forwarding_shim = true))]
2057#[deprecated(
2058    since = "0.26.0",
2059    note = "use `with_stream` or `with_device` around `softmax_axis`"
2060)]
2061pub fn softmax_axis_device(
2062    a: impl AsRef<Array>,
2063    axis: i32,
2064    precise: impl Into<Option<bool>>,
2065    #[optional] stream: impl AsRef<Stream>,
2066) -> Result<Array> {
2067    crate::with_stream(stream.as_ref(), || softmax_axis(a, axis, precise))
2068}
2069
2070/// Similar to [`softmax_axes`] but with no axis specified.
2071pub fn softmax(a: impl AsRef<Array>, precise: impl Into<Option<bool>>) -> Result<Array> {
2072    let stream = Stream::thread_local_or_default();
2073    let precise = precise.into().unwrap_or(false);
2074    let s = stream.as_ref().as_ptr();
2075
2076    Array::try_from_op(|res| unsafe { mlx_sys::mlx_softmax(res, a.as_ref().as_ptr(), precise, s) })
2077}
2078
2079/// Compatibility shim for [`softmax`].
2080#[generate_macro(customize(forwarding_shim = true))]
2081#[deprecated(
2082    since = "0.26.0",
2083    note = "use `with_stream` or `with_device` around `softmax`"
2084)]
2085pub fn softmax_device(
2086    a: impl AsRef<Array>,
2087    precise: impl Into<Option<bool>>,
2088    #[optional] stream: impl AsRef<Stream>,
2089) -> Result<Array> {
2090    crate::with_stream(stream.as_ref(), || softmax(a, precise))
2091}
2092
2093/// See [`Array::sqrt`].
2094pub fn sqrt(a: impl AsRef<Array>) -> Result<Array> {
2095    a.as_ref().sqrt()
2096}
2097
2098/// Compatibility shim for [`sqrt`].
2099#[generate_macro(customize(forwarding_shim = true))]
2100#[deprecated(
2101    since = "0.26.0",
2102    note = "use `with_stream` or `with_device` around `sqrt`"
2103)]
2104pub fn sqrt_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2105    crate::with_stream(stream.as_ref(), || sqrt(a))
2106}
2107
2108/// See [`Array::square`].
2109pub fn square(a: impl AsRef<Array>) -> Result<Array> {
2110    a.as_ref().square()
2111}
2112
2113/// Compatibility shim for [`square`].
2114#[generate_macro(customize(forwarding_shim = true))]
2115#[deprecated(
2116    since = "0.26.0",
2117    note = "use `with_stream` or `with_device` around `square`"
2118)]
2119pub fn square_device(
2120    a: impl AsRef<Array>,
2121    #[optional] stream: impl AsRef<Stream>,
2122) -> Result<Array> {
2123    crate::with_stream(stream.as_ref(), || square(a))
2124}
2125
2126/// See [`Array::subtract`].
2127pub fn subtract(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
2128    a.as_ref().subtract(b)
2129}
2130
2131/// Compatibility shim for [`subtract`].
2132#[generate_macro(customize(forwarding_shim = true))]
2133#[deprecated(
2134    since = "0.26.0",
2135    note = "use `with_stream` or `with_device` around `subtract`"
2136)]
2137pub fn subtract_device(
2138    a: impl AsRef<Array>,
2139    b: impl AsRef<Array>,
2140    #[optional] stream: impl AsRef<Stream>,
2141) -> Result<Array> {
2142    crate::with_stream(stream.as_ref(), || subtract(a, b))
2143}
2144
2145/// See [`Array::tan`].
2146pub fn tan(a: impl AsRef<Array>) -> Result<Array> {
2147    let stream = Stream::thread_local_or_default();
2148    Array::try_from_op(|res| unsafe {
2149        mlx_sys::mlx_tan(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2150    })
2151}
2152
2153/// Compatibility shim for [`tan`].
2154#[generate_macro(customize(forwarding_shim = true))]
2155#[deprecated(
2156    since = "0.26.0",
2157    note = "use `with_stream` or `with_device` around `tan`"
2158)]
2159pub fn tan_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2160    crate::with_stream(stream.as_ref(), || tan(a))
2161}
2162
2163/// Element-wise hyperbolic tangent.
2164pub fn tanh(a: impl AsRef<Array>) -> Result<Array> {
2165    let stream = Stream::thread_local_or_default();
2166    Array::try_from_op(|res| unsafe {
2167        mlx_sys::mlx_tanh(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2168    })
2169}
2170
2171/// Compatibility shim for [`tanh`].
2172#[generate_macro(customize(forwarding_shim = true))]
2173#[deprecated(
2174    since = "0.26.0",
2175    note = "use `with_stream` or `with_device` around `tanh`"
2176)]
2177pub fn tanh_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2178    crate::with_stream(stream.as_ref(), || tanh(a))
2179}
2180
2181/// Element-wise real part from a complex array.
2182pub fn real(a: impl AsRef<Array>) -> Result<Array> {
2183    let stream = Stream::thread_local_or_default();
2184    Array::try_from_op(|res| unsafe {
2185        mlx_sys::mlx_real(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2186    })
2187}
2188
2189/// Compatibility shim for [`real`].
2190#[generate_macro(customize(forwarding_shim = true))]
2191#[deprecated(
2192    since = "0.26.0",
2193    note = "use `with_stream` or `with_device` around `real`"
2194)]
2195pub fn real_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2196    crate::with_stream(stream.as_ref(), || real(a))
2197}
2198
2199/// Element-wise imaginary part from a complex array.
2200pub fn imag(a: impl AsRef<Array>) -> Result<Array> {
2201    let stream = Stream::thread_local_or_default();
2202    Array::try_from_op(|res| unsafe {
2203        mlx_sys::mlx_imag(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
2204    })
2205}
2206
2207/// Compatibility shim for [`imag`].
2208#[generate_macro(customize(forwarding_shim = true))]
2209#[deprecated(
2210    since = "0.26.0",
2211    note = "use `with_stream` or `with_device` around `imag`"
2212)]
2213pub fn imag_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
2214    crate::with_stream(stream.as_ref(), || imag(a))
2215}
2216
2217/// Matrix multiplication with block masking.
2218///
2219/// See the [python API docs](
2220/// https://ml-explore.github.io/mlx/build/html/python/_autosummary/mlx.core.block_masked_mm.html#mlx.core.block_masked_mm
2221/// ) for more information.
2222pub fn block_masked_mm<'mo, 'lhs, 'rhs>(
2223    a: impl AsRef<Array>,
2224    b: impl AsRef<Array>,
2225    block_size: impl Into<Option<i32>>,
2226    mask_out: impl Into<Option<&'mo Array>>,
2227    mask_lhs: impl Into<Option<&'lhs Array>>,
2228    mask_rhs: impl Into<Option<&'rhs Array>>,
2229) -> Result<Array> {
2230    let stream = Stream::thread_local_or_default();
2231    let a_ptr = a.as_ref().as_ptr();
2232    let b_ptr = b.as_ref().as_ptr();
2233    unsafe {
2234        let mask_out_ptr = mask_out
2235            .into()
2236            .map(|m| m.as_ptr())
2237            .unwrap_or(mlx_sys::mlx_array_new());
2238        let mask_lhs_ptr = mask_lhs
2239            .into()
2240            .map(|m| m.as_ptr())
2241            .unwrap_or(mlx_sys::mlx_array_new());
2242        let mask_rhs_ptr = mask_rhs
2243            .into()
2244            .map(|m| m.as_ptr())
2245            .unwrap_or(mlx_sys::mlx_array_new());
2246
2247        Array::try_from_op(|res| {
2248            mlx_sys::mlx_block_masked_mm(
2249                res,
2250                a_ptr,
2251                b_ptr,
2252                block_size.into().unwrap_or(32),
2253                mask_out_ptr,
2254                mask_lhs_ptr,
2255                mask_rhs_ptr,
2256                stream.as_ref().as_ptr(),
2257            )
2258        })
2259    }
2260}
2261
2262/// Compatibility shim for [`block_masked_mm`].
2263#[generate_macro(customize(forwarding_shim = true))]
2264#[deprecated(
2265    since = "0.26.0",
2266    note = "use `with_stream` or `with_device` around `block_masked_mm`"
2267)]
2268pub fn block_masked_mm_device<'mo, 'lhs, 'rhs>(
2269    a: impl AsRef<Array>,
2270    b: impl AsRef<Array>,
2271    #[optional] block_size: impl Into<Option<i32>>,
2272    #[optional] mask_out: impl Into<Option<&'mo Array>>,
2273    #[optional] mask_lhs: impl Into<Option<&'lhs Array>>,
2274    #[optional] mask_rhs: impl Into<Option<&'rhs Array>>,
2275    #[optional] stream: impl AsRef<Stream>,
2276) -> Result<Array> {
2277    crate::with_stream(stream.as_ref(), || {
2278        block_masked_mm(a, b, block_size, mask_out, mask_lhs, mask_rhs)
2279    })
2280}
2281
2282/// Matrix multiplication with addition and optional scaling.
2283///
2284/// Perform the (possibly batched) matrix multiplication of two arrays and add to the result with
2285/// optional scaling factors.
2286///
2287/// # Params
2288///
2289/// - `c`: input array,
2290/// - `a`: input array,
2291/// - `b`: input array,
2292/// - `alpha`: Scaling factor for the matrix product of `a` and `b` (default: `1`)
2293/// - `beta`: Scaling factor for `c` (default: `1`)
2294pub fn addmm(
2295    c: impl AsRef<Array>,
2296    a: impl AsRef<Array>,
2297    b: impl AsRef<Array>,
2298    alpha: impl Into<Option<f32>>,
2299    beta: impl Into<Option<f32>>,
2300) -> Result<Array> {
2301    let stream = Stream::thread_local_or_default();
2302    let c_ptr = c.as_ref().as_ptr();
2303    let a_ptr = a.as_ref().as_ptr();
2304    let b_ptr = b.as_ref().as_ptr();
2305    let alpha = alpha.into().unwrap_or(1.0);
2306    let beta = beta.into().unwrap_or(1.0);
2307
2308    Array::try_from_op(|res| unsafe {
2309        mlx_sys::mlx_addmm(
2310            res,
2311            c_ptr,
2312            a_ptr,
2313            b_ptr,
2314            alpha,
2315            beta,
2316            stream.as_ref().as_ptr(),
2317        )
2318    })
2319}
2320
2321/// Compatibility shim for [`addmm`].
2322#[generate_macro(customize(forwarding_shim = true))]
2323#[deprecated(
2324    since = "0.26.0",
2325    note = "use `with_stream` or `with_device` around `addmm`"
2326)]
2327pub fn addmm_device(
2328    c: impl AsRef<Array>,
2329    a: impl AsRef<Array>,
2330    b: impl AsRef<Array>,
2331    #[optional] alpha: impl Into<Option<f32>>,
2332    #[optional] beta: impl Into<Option<f32>>,
2333    #[optional] stream: impl AsRef<Stream>,
2334) -> Result<Array> {
2335    crate::with_stream(stream.as_ref(), || addmm(c, a, b, alpha, beta))
2336}
2337
2338/// Ordinary inner product of vectors for 1-D arrays, in higher dimensions a sum product over the
2339/// last axes.
2340pub fn inner(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
2341    let stream = Stream::thread_local_or_default();
2342    let a = a.as_ref();
2343    let b = b.as_ref();
2344    Array::try_from_op(|res| unsafe {
2345        mlx_sys::mlx_inner(res, a.as_ptr(), b.as_ptr(), stream.as_ref().as_ptr())
2346    })
2347}
2348
2349/// Compatibility shim for [`inner`].
2350#[generate_macro(customize(forwarding_shim = true))]
2351#[deprecated(
2352    since = "0.26.0",
2353    note = "use `with_stream` or `with_device` around `inner`"
2354)]
2355pub fn inner_device(
2356    a: impl AsRef<Array>,
2357    b: impl AsRef<Array>,
2358    #[optional] stream: impl AsRef<Stream>,
2359) -> Result<Array> {
2360    crate::with_stream(stream.as_ref(), || inner(a, b))
2361}
2362
2363/// Compute the outer product of two 1-D arrays, if the array’s passed are not 1-D a flatten op will
2364/// be run beforehand.
2365pub fn outer(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
2366    let stream = Stream::thread_local_or_default();
2367    let a = a.as_ref();
2368    let b = b.as_ref();
2369    Array::try_from_op(|res| unsafe {
2370        mlx_sys::mlx_outer(res, a.as_ptr(), b.as_ptr(), stream.as_ref().as_ptr())
2371    })
2372}
2373
2374/// Compatibility shim for [`outer`].
2375#[generate_macro(customize(forwarding_shim = true))]
2376#[deprecated(
2377    since = "0.26.0",
2378    note = "use `with_stream` or `with_device` around `outer`"
2379)]
2380pub fn outer_device(
2381    a: impl AsRef<Array>,
2382    b: impl AsRef<Array>,
2383    #[optional] stream: impl AsRef<Stream>,
2384) -> Result<Array> {
2385    crate::with_stream(stream.as_ref(), || outer(a, b))
2386}
2387
2388/// Compute the tensor dot product along the specified axes.
2389pub fn tensordot_axes(
2390    a: impl AsRef<Array>,
2391    b: impl AsRef<Array>,
2392    axes_a: &[i32],
2393    axes_b: &[i32],
2394) -> Result<Array> {
2395    let stream = Stream::thread_local_or_default();
2396    let a = a.as_ref();
2397    let b = b.as_ref();
2398    Array::try_from_op(|res| unsafe {
2399        mlx_sys::mlx_tensordot(
2400            res,
2401            a.as_ptr(),
2402            b.as_ptr(),
2403            axes_a.as_ptr(),
2404            axes_a.len(),
2405            axes_b.as_ptr(),
2406            axes_b.len(),
2407            stream.as_ref().as_ptr(),
2408        )
2409    })
2410}
2411
2412/// Compatibility shim for [`tensordot_axes`].
2413#[generate_macro(customize(forwarding_shim = true))]
2414#[deprecated(
2415    since = "0.26.0",
2416    note = "use `with_stream` or `with_device` around `tensordot_axes`"
2417)]
2418pub fn tensordot_axes_device(
2419    a: impl AsRef<Array>,
2420    b: impl AsRef<Array>,
2421    axes_a: &[i32],
2422    axes_b: &[i32],
2423    #[optional] stream: impl AsRef<Stream>,
2424) -> Result<Array> {
2425    crate::with_stream(stream.as_ref(), || tensordot_axes(a, b, axes_a, axes_b))
2426}
2427
2428/// Similar to [`tensordot_axes`] but with a single axis.
2429pub fn tensordot_axis(a: impl AsRef<Array>, b: impl AsRef<Array>, axis: i32) -> Result<Array> {
2430    let stream = Stream::thread_local_or_default();
2431    let a = a.as_ref();
2432    let b = b.as_ref();
2433    Array::try_from_op(|res| unsafe {
2434        mlx_sys::mlx_tensordot_axis(res, a.as_ptr(), b.as_ptr(), axis, stream.as_ref().as_ptr())
2435    })
2436}
2437
2438/// Compatibility shim for [`tensordot_axis`].
2439#[generate_macro(customize(forwarding_shim = true))]
2440#[deprecated(
2441    since = "0.26.0",
2442    note = "use `with_stream` or `with_device` around `tensordot_axis`"
2443)]
2444pub fn tensordot_axis_device(
2445    a: impl AsRef<Array>,
2446    b: impl AsRef<Array>,
2447    axis: i32,
2448    #[optional] stream: impl AsRef<Stream>,
2449) -> Result<Array> {
2450    crate::with_stream(stream.as_ref(), || tensordot_axis(a, b, axis))
2451}
2452
2453/// Matrix multiplication with gathered indices.
2454///
2455/// Perform matrix multiplication with index gathering along the batch dimensions.
2456/// This is useful for operations where different batch elements should use different
2457/// matrices from a pool.
2458///
2459/// # Params
2460///
2461/// - `a`: Input array
2462/// - `b`: Input array
2463/// - `lhs_indices`: Optional indices to gather from `a`'s batch dimensions
2464/// - `rhs_indices`: Optional indices to gather from `b`'s batch dimensions
2465/// - `sorted_indices`: If true, indicates the indices are sorted which can enable
2466///   optimizations (default: false)
2467///
2468/// # Example
2469///
2470/// ```rust
2471/// use mlx_rs::{Array, ops::gather_mm};
2472///
2473/// let a = Array::ones::<f32>(&[5, 32, 32]).unwrap();
2474/// let b = Array::ones::<f32>(&[3, 32, 32]).unwrap();
2475/// let lhs_indices = Array::from_slice(&[0u32, 2], &[2]);
2476/// let rhs_indices = Array::from_slice(&[2u32, 1], &[2]);
2477/// let result = gather_mm(&a, &b, &lhs_indices, &rhs_indices, None).unwrap();
2478/// // result has shape [2, 32, 32]
2479/// ```
2480pub fn gather_mm<'lhs, 'rhs>(
2481    a: impl AsRef<Array>,
2482    b: impl AsRef<Array>,
2483    lhs_indices: impl Into<Option<&'lhs Array>>,
2484    rhs_indices: impl Into<Option<&'rhs Array>>,
2485    sorted_indices: impl Into<Option<bool>>,
2486) -> Result<Array> {
2487    let stream = Stream::thread_local_or_default();
2488    let a_ptr = a.as_ref().as_ptr();
2489    let b_ptr = b.as_ref().as_ptr();
2490    let sorted = sorted_indices.into().unwrap_or(false);
2491
2492    unsafe {
2493        let lhs_ptr = lhs_indices
2494            .into()
2495            .map(|i| i.as_ptr())
2496            .unwrap_or(mlx_sys::mlx_array_new());
2497        let rhs_ptr = rhs_indices
2498            .into()
2499            .map(|i| i.as_ptr())
2500            .unwrap_or(mlx_sys::mlx_array_new());
2501
2502        Array::try_from_op(|res| {
2503            mlx_sys::mlx_gather_mm(
2504                res,
2505                a_ptr,
2506                b_ptr,
2507                lhs_ptr,
2508                rhs_ptr,
2509                sorted,
2510                stream.as_ref().as_ptr(),
2511            )
2512        })
2513    }
2514}
2515
2516/// Compatibility shim for [`gather_mm`].
2517#[generate_macro(customize(forwarding_shim = true))]
2518#[deprecated(
2519    since = "0.26.0",
2520    note = "use `with_stream` or `with_device` around `gather_mm`"
2521)]
2522pub fn gather_mm_device<'lhs, 'rhs>(
2523    a: impl AsRef<Array>,
2524    b: impl AsRef<Array>,
2525    #[optional] lhs_indices: impl Into<Option<&'lhs Array>>,
2526    #[optional] rhs_indices: impl Into<Option<&'rhs Array>>,
2527    #[optional] sorted_indices: impl Into<Option<bool>>,
2528    #[optional] stream: impl AsRef<Stream>,
2529) -> Result<Array> {
2530    crate::with_stream(stream.as_ref(), || {
2531        gather_mm(a, b, lhs_indices, rhs_indices, sorted_indices)
2532    })
2533}
2534
2535#[cfg(test)]
2536mod tests {
2537    use std::f32::consts::PI;
2538
2539    use super::*;
2540    use crate::{
2541        array, complex64,
2542        ops::{all_close, arange, broadcast_to, eye, full, linspace, ones, reshape, split_equal},
2543        test_utils::{assert_array_eq, assert_array_eq_with_context, tolerances},
2544        transforms::eval,
2545        Dtype,
2546    };
2547    use float_eq::assert_float_eq;
2548    use pretty_assertions::assert_eq;
2549
2550    #[test]
2551    fn test_abs() {
2552        let data = [1i32, 2, -3, -4, -5];
2553        let array = Array::from_slice(&data, &[5]);
2554        let result = array.abs().unwrap();
2555
2556        let data: &[i32] = result.as_slice();
2557        assert_eq!(data, [1, 2, 3, 4, 5]);
2558
2559        // test that previous array is not modified and valid
2560        let data: &[i32] = array.as_slice();
2561        assert_eq!(data, [1, 2, -3, -4, -5]);
2562    }
2563
2564    #[test]
2565    fn test_add() {
2566        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2567        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2568
2569        let c = &a + &b;
2570
2571        let c_data: &[f32] = c.as_slice();
2572        assert_eq!(c_data, &[5.0, 7.0, 9.0]);
2573
2574        // check a and b are not modified
2575        let a_data: &[f32] = a.as_slice();
2576        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2577
2578        let b_data: &[f32] = b.as_slice();
2579        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2580    }
2581
2582    #[test]
2583    fn test_add_invalid_broadcast() {
2584        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2585        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2586
2587        let c = a.add(&b);
2588        assert!(c.is_err());
2589    }
2590
2591    #[test]
2592    fn test_sub() {
2593        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2594        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2595
2596        let c = &a - &b;
2597
2598        let c_data: &[f32] = c.as_slice();
2599        assert_eq!(c_data, &[-3.0, -3.0, -3.0]);
2600
2601        // check a and b are not modified
2602        let a_data: &[f32] = a.as_slice();
2603        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2604
2605        let b_data: &[f32] = b.as_slice();
2606        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2607    }
2608
2609    #[test]
2610    fn test_sub_invalid_broadcast() {
2611        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2612        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2613        let c = a.subtract(&b);
2614        assert!(c.is_err());
2615    }
2616
2617    #[test]
2618    fn test_neg() {
2619        let a = Array::from_slice::<f32>(&[1.0, 2.0, 3.0], &[3]);
2620        let b = a.negative().unwrap();
2621
2622        let b_data: &[f32] = b.as_slice();
2623        assert_eq!(b_data, &[-1.0, -2.0, -3.0]);
2624
2625        // check a is not modified
2626        let a_data: &[f32] = a.as_slice();
2627        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2628    }
2629
2630    #[test]
2631    fn test_neg_bool() {
2632        let a = Array::from_slice(&[true, false, true], &[3]);
2633        let b = a.negative();
2634        assert!(b.is_err());
2635    }
2636
2637    #[test]
2638    fn test_logical_not() {
2639        let a: Array = false.into();
2640        let b = a.logical_not().unwrap();
2641
2642        let b_data: &[bool] = b.as_slice();
2643        assert_eq!(b_data, [true]);
2644    }
2645
2646    #[test]
2647    fn test_mul() {
2648        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2649        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2650
2651        let c = &a * &b;
2652
2653        let c_data: &[f32] = c.as_slice();
2654        assert_eq!(c_data, &[4.0, 10.0, 18.0]);
2655
2656        // check a and b are not modified
2657        let a_data: &[f32] = a.as_slice();
2658        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2659
2660        let b_data: &[f32] = b.as_slice();
2661        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2662    }
2663
2664    #[test]
2665    fn test_mul_invalid_broadcast() {
2666        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2667        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2668        let c = a.multiply(&b);
2669        assert!(c.is_err());
2670    }
2671
2672    #[test]
2673    fn test_nan_to_num() {
2674        let a = array!([1.0, 2.0, f32::NAN, 4.0, 5.0]);
2675        let b = a.nan_to_num(0.0, 1.0, 0.0).unwrap();
2676
2677        let b_data: &[f32] = b.as_slice();
2678        assert_eq!(b_data, &[1.0, 2.0, 0.0, 4.0, 5.0]);
2679    }
2680
2681    #[test]
2682    fn test_div() {
2683        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2684        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2685
2686        let c = &a / &b;
2687
2688        let c_data: &[f32] = c.as_slice();
2689        assert_eq!(c_data, &[0.25, 0.4, 0.5]);
2690
2691        // check a and b are not modified
2692        let a_data: &[f32] = a.as_slice();
2693        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2694
2695        let b_data: &[f32] = b.as_slice();
2696        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2697    }
2698
2699    #[test]
2700    fn test_div_invalid_broadcast() {
2701        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2702        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2703        let c = a.divide(&b);
2704        assert!(c.is_err());
2705    }
2706
2707    #[test]
2708    fn test_pow() {
2709        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2710        let b = Array::from_slice(&[2.0, 3.0, 4.0], &[3]);
2711
2712        let c = a.power(&b).unwrap();
2713
2714        assert_array_eq(
2715            &c,
2716            array!([1.0, 8.0, 81.0]),
2717            tolerances::MLX_DEFAULT.rtol,
2718            tolerances::MLX_DEFAULT.atol,
2719        );
2720
2721        // check a and b are not modified
2722        let a_data: &[f32] = a.as_slice();
2723        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2724
2725        let b_data: &[f32] = b.as_slice();
2726        assert_eq!(b_data, &[2.0, 3.0, 4.0]);
2727    }
2728
2729    #[test]
2730    fn test_pow_invalid_broadcast() {
2731        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2732        let b = Array::from_slice(&[2.0, 3.0], &[2]);
2733        let c = a.power(&b);
2734        assert!(c.is_err());
2735    }
2736
2737    #[test]
2738    fn test_rem() {
2739        let a = Array::from_slice(&[10.0, 11.0, 12.0], &[3]);
2740        let b = Array::from_slice(&[3.0, 4.0, 5.0], &[3]);
2741
2742        let c = &a % &b;
2743
2744        let c_data: &[f32] = c.as_slice();
2745        assert_eq!(c_data, &[1.0, 3.0, 2.0]);
2746
2747        // check a and b are not modified
2748        let a_data: &[f32] = a.as_slice();
2749        assert_eq!(a_data, &[10.0, 11.0, 12.0]);
2750
2751        let b_data: &[f32] = b.as_slice();
2752        assert_eq!(b_data, &[3.0, 4.0, 5.0]);
2753    }
2754
2755    #[test]
2756    fn test_rem_invalid_broadcast() {
2757        let a = Array::from_slice(&[10.0, 11.0, 12.0], &[3]);
2758        let b = Array::from_slice(&[3.0, 4.0], &[2]);
2759        let c = a.remainder(&b);
2760        assert!(c.is_err());
2761    }
2762
2763    #[test]
2764    fn test_sqrt() {
2765        let a = Array::from_slice(&[1.0, 4.0, 9.0], &[3]);
2766        let b = a.sqrt().unwrap();
2767
2768        let b_data: &[f32] = b.as_slice();
2769        assert_eq!(b_data, &[1.0, 2.0, 3.0]);
2770
2771        // check a is not modified
2772        let a_data: &[f32] = a.as_slice();
2773        assert_eq!(a_data, &[1.0, 4.0, 9.0]);
2774    }
2775
2776    #[test]
2777    fn test_cos() {
2778        let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
2779        let b = a.cos().unwrap();
2780
2781        let b_expected = array!([1.0, 0.54030234, -0.41614687]);
2782        assert_array_all_close!(b, b_expected);
2783
2784        // check a is not modified
2785        let a_expected = array!([0.0, 1.0, 2.0]);
2786        assert_array_all_close!(a, a_expected);
2787    }
2788
2789    #[test]
2790    fn test_exp() {
2791        let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
2792        let b = a.exp().unwrap();
2793
2794        let b_expected = array!([1.0, 2.7182817, 7.389056]);
2795        assert_array_all_close!(b, b_expected);
2796
2797        // check a is not modified
2798        let a_expected = array!([0.0, 1.0, 2.0]);
2799        assert_array_all_close!(a, a_expected);
2800    }
2801
2802    #[test]
2803    fn test_floor() {
2804        let a = Array::from_slice(&[0.1, 1.9, 2.5], &[3]);
2805        let b = a.floor().unwrap();
2806
2807        let b_data: &[f32] = b.as_slice();
2808        assert_eq!(b_data, &[0.0, 1.0, 2.0]);
2809
2810        // check a is not modified
2811        let a_data: &[f32] = a.as_slice();
2812        assert_eq!(a_data, &[0.1, 1.9, 2.5]);
2813    }
2814
2815    #[test]
2816    fn test_floor_complex64() {
2817        let val = complex64::new(1.0, 2.0);
2818        let a = Array::from_complex(val);
2819        let b = a.floor();
2820        assert!(b.is_err());
2821    }
2822
2823    #[test]
2824    fn test_floor_divide() {
2825        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2826        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2827
2828        let c = a.floor_divide(&b).unwrap();
2829
2830        let c_data: &[f32] = c.as_slice();
2831        assert_eq!(c_data, &[0.0, 0.0, 0.0]);
2832
2833        // check a and b are not modified
2834        let a_data: &[f32] = a.as_slice();
2835        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2836
2837        let b_data: &[f32] = b.as_slice();
2838        assert_eq!(b_data, &[4.0, 5.0, 6.0]);
2839    }
2840
2841    #[test]
2842    fn test_floor_divide_complex64() {
2843        let val = complex64::new(1.0, 2.0);
2844        let a = Array::from_complex(val);
2845        let b = Array::from_slice(&[4.0, 5.0, 6.0], &[3]);
2846        let c = a.floor_divide(&b);
2847        assert!(c.is_err());
2848    }
2849
2850    #[test]
2851    fn test_floor_divide_invalid_broadcast() {
2852        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2853        let b = Array::from_slice(&[4.0, 5.0], &[2]);
2854        let c = a.floor_divide(&b);
2855        assert!(c.is_err());
2856    }
2857
2858    #[test]
2859    fn test_is_nan() {
2860        let a = Array::from_slice(&[1.0, f32::NAN, 3.0], &[3]);
2861        let b = a.is_nan().unwrap();
2862
2863        let b_data: &[bool] = b.as_slice();
2864        assert_eq!(b_data, &[false, true, false]);
2865    }
2866
2867    #[test]
2868    fn test_is_inf() {
2869        let a = Array::from_slice(&[1.0, f32::INFINITY, 3.0], &[3]);
2870        let b = a.is_inf().unwrap();
2871
2872        let b_data: &[bool] = b.as_slice();
2873        assert_eq!(b_data, &[false, true, false]);
2874    }
2875
2876    #[test]
2877    fn test_is_finite() {
2878        let a = Array::from_slice(&[1.0, f32::INFINITY, 3.0], &[3]);
2879        let b = a.is_finite().unwrap();
2880
2881        let b_data: &[bool] = b.as_slice();
2882        assert_eq!(b_data, &[true, false, true]);
2883    }
2884
2885    #[test]
2886    fn test_is_neg_inf() {
2887        let a = Array::from_slice(&[1.0, f32::NEG_INFINITY, 3.0], &[3]);
2888        let b = a.is_neg_inf().unwrap();
2889
2890        let b_data: &[bool] = b.as_slice();
2891        assert_eq!(b_data, &[false, true, false]);
2892    }
2893
2894    #[test]
2895    fn test_is_pos_inf() {
2896        let a = Array::from_slice(&[1.0, f32::INFINITY, 3.0], &[3]);
2897        let b = a.is_pos_inf().unwrap();
2898
2899        let b_data: &[bool] = b.as_slice();
2900        assert_eq!(b_data, &[false, true, false]);
2901    }
2902
2903    #[test]
2904    fn test_log() {
2905        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2906        let b = a.log().unwrap();
2907
2908        let b_data: &[f32] = b.as_slice();
2909        assert_eq!(b_data, &[0.0, 0.6931472, 1.0986123]);
2910
2911        // check a is not modified
2912        let a_data: &[f32] = a.as_slice();
2913        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2914    }
2915
2916    #[test]
2917    fn test_log2() {
2918        let a = Array::from_slice(&[1.0, 2.0, 4.0, 8.0], &[4]);
2919        let b = a.log2().unwrap();
2920
2921        let b_data: &[f32] = b.as_slice();
2922        assert_eq!(b_data, &[0.0, 1.0, 2.0, 3.0]);
2923
2924        // check a is not modified
2925        let a_data: &[f32] = a.as_slice();
2926        assert_eq!(a_data, &[1.0, 2.0, 4.0, 8.0]);
2927    }
2928
2929    #[test]
2930    fn test_log10() {
2931        let a = Array::from_slice(&[1.0, 10.0, 100.0], &[3]);
2932        let b = a.log10().unwrap();
2933
2934        let b_data: &[f32] = b.as_slice();
2935        assert_eq!(b_data, &[0.0, 1.0, 2.0]);
2936
2937        // check a is not modified
2938        let a_data: &[f32] = a.as_slice();
2939        assert_eq!(a_data, &[1.0, 10.0, 100.0]);
2940    }
2941
2942    #[test]
2943    fn test_log1p() {
2944        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
2945        let b = a.log1p().unwrap();
2946
2947        assert_array_eq(
2948            &b,
2949            array!([0.6931472, 1.0986123, 1.3862944]),
2950            tolerances::MLX_DEFAULT.rtol,
2951            tolerances::MLX_DEFAULT.atol,
2952        );
2953
2954        // check a is not modified
2955        let a_data: &[f32] = a.as_slice();
2956        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
2957    }
2958
2959    #[test]
2960    fn test_matmul() {
2961        let a = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
2962        let b = Array::from_slice(&[-5.0, 37.5, 4., 7., 1., 0.], &[2, 3]);
2963
2964        let c = a.matmul(&b).unwrap();
2965
2966        assert_eq!(c.shape(), &[2, 3]);
2967        let c_data: &[f32] = c.as_slice();
2968        assert_eq!(c_data, &[9.0, 39.5, 4.0, 13.0, 116.5, 12.0]);
2969
2970        // check a and b are not modified
2971        let a_data: &[i32] = a.as_slice();
2972        assert_eq!(a_data, &[1, 2, 3, 4]);
2973
2974        let b_data: &[f32] = b.as_slice();
2975        assert_eq!(b_data, &[-5.0, 37.5, 4., 7., 1., 0.]);
2976    }
2977
2978    #[test]
2979    fn test_matmul_ndim_zero() {
2980        let a: Array = 1.0.into();
2981        let b = Array::from_slice::<i32>(&[1], &[1]);
2982        let c = a.matmul(&b);
2983        assert!(c.is_err());
2984    }
2985
2986    #[test]
2987    fn test_matmul_ndim_one() {
2988        let a = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
2989        let b = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
2990        let c = a.matmul(&b);
2991        assert!(c.is_ok());
2992    }
2993
2994    #[test]
2995    fn test_matmul_dim_mismatch() {
2996        let a = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
2997        let b = Array::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], &[2, 5]);
2998        let c = a.matmul(&b);
2999        assert!(c.is_err());
3000    }
3001
3002    #[test]
3003    fn test_matmul_non_float_output_type() {
3004        let a = Array::from_slice(&[1, 2, 3, 4], &[2, 2]);
3005        let b = Array::from_slice(&[5, 37, 4, 7, 1, 0], &[2, 3]);
3006
3007        let c = a.matmul(&b);
3008        assert!(c.is_err());
3009    }
3010
3011    #[test]
3012    fn test_reciprocal() {
3013        let a = Array::from_slice(&[1.0, 2.0, 4.0], &[3]);
3014        let b = a.reciprocal().unwrap();
3015
3016        let b_data: &[f32] = b.as_slice();
3017        assert_eq!(b_data, &[1.0, 0.5, 0.25]);
3018
3019        // check a is not modified
3020        let a_data: &[f32] = a.as_slice();
3021        assert_eq!(a_data, &[1.0, 2.0, 4.0]);
3022    }
3023
3024    #[test]
3025    fn test_round() {
3026        let a = Array::from_slice(&[1.1, 2.9, 3.5], &[3]);
3027        let b = a.round(None).unwrap();
3028
3029        let b_data: &[f32] = b.as_slice();
3030        assert_eq!(b_data, &[1.0, 3.0, 4.0]);
3031
3032        // check a is not modified
3033        let a_data: &[f32] = a.as_slice();
3034        assert_eq!(a_data, &[1.1, 2.9, 3.5]);
3035    }
3036
3037    #[test]
3038    fn test_rsqrt() {
3039        let a = Array::from_slice(&[1.0, 2.0, 4.0], &[3]);
3040        let b = a.rsqrt().unwrap();
3041
3042        let b_data: &[f32] = b.as_slice();
3043        assert_eq!(b_data, &[1.0, 0.70710677, 0.5]);
3044
3045        // check a is not modified
3046        let a_data: &[f32] = a.as_slice();
3047        assert_eq!(a_data, &[1.0, 2.0, 4.0]);
3048    }
3049
3050    #[test]
3051    fn test_sin() {
3052        let a = Array::from_slice(&[0.0, 1.0, 2.0], &[3]);
3053        let b = a.sin().unwrap();
3054
3055        assert_array_eq(
3056            &b,
3057            array!([0.0, 0.841471, 0.9092974]),
3058            tolerances::MLX_DEFAULT.rtol,
3059            tolerances::MLX_DEFAULT.atol,
3060        );
3061
3062        // check a is not modified
3063        let a_data: &[f32] = a.as_slice();
3064        assert_eq!(a_data, &[0.0, 1.0, 2.0]);
3065    }
3066
3067    #[test]
3068    fn test_square() {
3069        let a = Array::from_slice(&[1.0, 2.0, 3.0], &[3]);
3070        let b = a.square().unwrap();
3071
3072        let b_data: &[f32] = b.as_slice();
3073        assert_eq!(b_data, &[1.0, 4.0, 9.0]);
3074
3075        // check a is not modified
3076        let a_data: &[f32] = a.as_slice();
3077        assert_eq!(a_data, &[1.0, 2.0, 3.0]);
3078    }
3079
3080    // The unit tests below are adapted from the original mlx c++ codebase.
3081
3082    #[test]
3083    fn test_unary_neg() {
3084        let x = array!(1.0);
3085        assert_eq!(negative(&x).unwrap().item_exact::<f32>(), -1.0);
3086        assert_eq!((-x).item_exact::<f32>(), -1.0);
3087
3088        // works on empty array
3089        assert_array_eq(
3090            -array!(),
3091            array!(),
3092            tolerances::EXACT.rtol,
3093            tolerances::EXACT.atol,
3094        );
3095
3096        // Throws on bool
3097        let x = array!(true);
3098        assert!(negative(&x).is_err());
3099    }
3100
3101    #[test]
3102    fn test_unary_abs() {
3103        let x = array!([-1.0, 0.0, 1.0]);
3104        assert_array_eq(
3105            abs(&x).unwrap(),
3106            array!([1.0, 0.0, 1.0]),
3107            tolerances::EXACT.rtol,
3108            tolerances::EXACT.atol,
3109        );
3110
3111        // works on empty array
3112        assert_array_eq(
3113            abs(array!()).unwrap(),
3114            array!(),
3115            tolerances::EXACT.rtol,
3116            tolerances::EXACT.atol,
3117        );
3118
3119        // int32
3120        let x = array!([-1, 0, 1]);
3121        assert_array_eq(
3122            abs(&x).unwrap(),
3123            array!([1, 0, 1]),
3124            tolerances::EXACT.rtol,
3125            tolerances::EXACT.atol,
3126        );
3127
3128        // uint32
3129        let x = array!([1u32, 0, 1]);
3130        assert_array_eq(
3131            abs(&x).unwrap(),
3132            array!([1u32, 0, 1]),
3133            tolerances::EXACT.rtol,
3134            tolerances::EXACT.atol,
3135        );
3136
3137        // bool
3138        let x = array!([false, true]);
3139        assert_array_eq(
3140            abs(&x).unwrap(),
3141            array!([false, true]),
3142            tolerances::EXACT.rtol,
3143            tolerances::EXACT.atol,
3144        );
3145    }
3146
3147    #[test]
3148    fn test_unary_sign() {
3149        let x = array!([-1.0, 0.0, 1.0]);
3150        assert_array_eq(
3151            sign(&x).unwrap(),
3152            x,
3153            tolerances::EXACT.rtol,
3154            tolerances::EXACT.atol,
3155        );
3156
3157        // works on empty array
3158        assert_array_eq(
3159            sign(array!()).unwrap(),
3160            array!(),
3161            tolerances::EXACT.rtol,
3162            tolerances::EXACT.atol,
3163        );
3164
3165        // int32
3166        let x = array!([-1, 0, 1]);
3167        assert_array_eq(
3168            sign(&x).unwrap(),
3169            x,
3170            tolerances::EXACT.rtol,
3171            tolerances::EXACT.atol,
3172        );
3173
3174        // uint32
3175        let x = array!([1u32, 0, 1]);
3176        assert_array_eq(
3177            sign(&x).unwrap(),
3178            x,
3179            tolerances::EXACT.rtol,
3180            tolerances::EXACT.atol,
3181        );
3182
3183        // bool
3184        let x = array!([false, true]);
3185        assert_array_eq(
3186            sign(&x).unwrap(),
3187            x,
3188            tolerances::EXACT.rtol,
3189            tolerances::EXACT.atol,
3190        );
3191    }
3192
3193    const NEG_INF: f32 = f32::NEG_INFINITY;
3194
3195    #[test]
3196    fn test_unary_floor_ceil() {
3197        let x = array![1.0];
3198        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), 1.0);
3199        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), 1.0);
3200
3201        let x = array![1.5];
3202        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), 1.0);
3203        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), 2.0);
3204
3205        let x = array![-1.5];
3206        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), -2.0);
3207        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), -1.0);
3208
3209        let x = array![NEG_INF];
3210        assert_eq!(floor(&x).unwrap().item_exact::<f32>(), NEG_INF);
3211        assert_eq!(ceil(&x).unwrap().item_exact::<f32>(), NEG_INF);
3212
3213        let x = array!([1.0, 1.0]).as_type::<complex64>().unwrap();
3214        assert!(floor(&x).is_err());
3215        assert!(ceil(&x).is_err());
3216    }
3217
3218    #[test]
3219    fn test_unary_round() {
3220        let x = array!([0.5, -0.5, 1.5, -1.5, 2.3, 2.6]);
3221        assert_array_eq_with_context(
3222            round(&x, None).unwrap(),
3223            array!([0.0_f32, 0.0, 2.0, -2.0, 2.0, 3.0]),
3224            tolerances::EXACT.rtol,
3225            tolerances::EXACT.atol,
3226            "float32 half-to-even rounding",
3227        );
3228
3229        let x = array!([11, 222, 32]);
3230        assert_array_eq_with_context(
3231            round(&x, -1).unwrap(),
3232            array!([10, 220, 30]),
3233            tolerances::EXACT.rtol,
3234            tolerances::EXACT.atol,
3235            "int32 negative-decimal rounding",
3236        );
3237    }
3238
3239    #[test]
3240    fn test_unary_exp() {
3241        let x = array![0.0];
3242        assert_eq!(exp(&x).unwrap().item_exact::<f32>(), 1.0);
3243
3244        let x = array![2.0];
3245        assert_float_eq! {
3246            exp(&x).unwrap().item_exact::<f32>(),
3247            2.0f32.exp(),
3248            abs <= 1e-5
3249        };
3250
3251        assert_array_eq(
3252            exp(array!()).unwrap(),
3253            array!(),
3254            tolerances::EXACT.rtol,
3255            tolerances::EXACT.atol,
3256        );
3257
3258        let x = array![NEG_INF];
3259        assert_eq!(exp(&x).unwrap().item_exact::<f32>(), 0.0);
3260
3261        // Integer input type
3262        let x = array![2];
3263        assert_eq!(x.dtype(), Dtype::Int32);
3264        assert_float_eq! {
3265            exp(&x).unwrap().item_exact::<f32>(),
3266            2.0f32.exp(),
3267            abs <= 1e-5
3268        };
3269
3270        // Input is irregularly strided
3271        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3272        let res = exp(&x).unwrap();
3273        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.exp())).unwrap();
3274        assert!(all_close(&res, &expected, None, None, None).unwrap());
3275
3276        let data = Array::from_slice(&[0.0, 1.0, 2.0, 3.0], &[2, 2]);
3277        let x = split_equal(&data, 2, 1).unwrap();
3278        let expected = Array::from_slice(&[0.0f32.exp(), 2.0f32.exp()], &[2, 1]);
3279        assert!(all_close(exp(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3280    }
3281
3282    #[test]
3283    fn test_unary_expm1() {
3284        let x = array![-1.0];
3285        assert_float_eq! {
3286            expm1(&x).unwrap().item_exact::<f32>(),
3287            (-1.0f32).exp_m1(),
3288            abs <= 1e-5
3289        };
3290
3291        let x = array![1.0];
3292        assert_float_eq! {
3293            expm1(&x).unwrap().item_exact::<f32>(),
3294            1.0f32.exp_m1(),
3295            abs <= 1e-5
3296        };
3297
3298        // Integer input type
3299        let x = array![1];
3300        assert_eq!(expm1(&x).unwrap().dtype(), Dtype::Float32);
3301        assert_float_eq! {
3302            expm1(&x).unwrap().item_exact::<f32>(),
3303            1.0f32.exp_m1(),
3304            abs <= 1e-5
3305        };
3306    }
3307
3308    #[test]
3309    fn test_unary_sin() {
3310        let x = array![0.0];
3311        assert_eq!(sin(&x).unwrap().item_exact::<f32>(), 0.0);
3312
3313        let x = array![std::f32::consts::PI / 2.0];
3314        assert_float_eq! {
3315            sin(&x).unwrap().item_exact::<f32>(),
3316            (std::f32::consts::PI / 2.0f32).sin(),
3317            abs <= 1e-5
3318        };
3319
3320        assert_array_eq(
3321            sin(array!()).unwrap(),
3322            array!(),
3323            tolerances::EXACT.rtol,
3324            tolerances::EXACT.atol,
3325        );
3326
3327        // Integer input type
3328        let x = array![0];
3329        assert_eq!(x.dtype(), Dtype::Int32);
3330        assert_float_eq! {
3331            sin(&x).unwrap().item_exact::<f32>(),
3332            0.0f32.sin(),
3333            abs <= 1e-5
3334        };
3335
3336        // Input is irregularly strided
3337        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3338        let res = sin(&x).unwrap();
3339        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.sin())).unwrap();
3340        assert!(all_close(&res, &expected, None, None, None).unwrap());
3341
3342        let data = Array::from_slice(&[0.0, 1.0, 2.0, 3.0], &[2, 2]);
3343        let x = split_equal(&data, 2, 1).unwrap();
3344        let expected = Array::from_slice(&[0.0f32.sin(), 2.0f32.sin()], &[2, 1]);
3345        assert!(all_close(sin(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3346    }
3347
3348    #[test]
3349    fn test_unary_cos() {
3350        let x = array![0.0];
3351        assert_float_eq! {
3352            cos(&x).unwrap().item_exact::<f32>(),
3353            0.0f32.cos(),
3354            abs <= 1e-5
3355        };
3356
3357        let x = array![std::f32::consts::PI / 2.0];
3358        assert_float_eq! {
3359            cos(&x).unwrap().item_exact::<f32>(),
3360            (std::f32::consts::PI / 2.0f32).cos(),
3361            abs <= 1e-5
3362        };
3363
3364        assert_array_eq(
3365            cos(array!()).unwrap(),
3366            array!(),
3367            tolerances::EXACT.rtol,
3368            tolerances::EXACT.atol,
3369        );
3370
3371        // Integer input type
3372        let x = array![0];
3373        assert_eq!(x.dtype(), Dtype::Int32);
3374        assert_float_eq! {
3375            cos(&x).unwrap().item_exact::<f32>(),
3376            0.0f32.cos(),
3377            abs <= 1e-5
3378        };
3379
3380        // Input is irregularly strided
3381        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3382        let res = cos(&x).unwrap();
3383        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.cos())).unwrap();
3384        assert!(all_close(&res, &expected, None, None, None).unwrap());
3385
3386        let data = Array::from_slice(&[0.0, 1.0, 2.0, 3.0], &[2, 2]);
3387        let x = split_equal(&data, 2, 1).unwrap();
3388        let expected = Array::from_slice(&[0.0f32.cos(), 2.0f32.cos()], &[2, 1]);
3389        assert!(all_close(cos(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3390    }
3391
3392    #[test]
3393    fn test_unary_degrees() {
3394        let x = array![0.0];
3395        assert_eq!(degrees(&x).unwrap().item_exact::<f32>(), 0.0);
3396
3397        let x = array![std::f32::consts::PI / 2.0];
3398        assert_eq!(degrees(&x).unwrap().item_exact::<f32>(), 90.0);
3399
3400        assert_array_eq(
3401            degrees(array!()).unwrap(),
3402            array!(),
3403            tolerances::EXACT.rtol,
3404            tolerances::EXACT.atol,
3405        );
3406
3407        // Integer input type
3408        let x = array![0];
3409        assert_eq!(x.dtype(), Dtype::Int32);
3410        assert_eq!(degrees(&x).unwrap().item_exact::<f32>(), 0.0);
3411
3412        // Input is irregularly strided
3413        let x = broadcast_to(&array!(std::f32::consts::PI / 2.0), &[2, 2, 2]).unwrap();
3414        let res = degrees(&x).unwrap();
3415        let expected = Array::full::<f32>(&[2, 2, 2], array!(90.0)).unwrap();
3416        assert!(all_close(&res, &expected, None, None, None).unwrap());
3417
3418        let angles = Array::from_slice(&[0.0, PI / 2.0, PI, 1.5 * PI], &[2, 2]);
3419        let x = split_equal(&angles, 2, 1).unwrap();
3420        let expected = Array::from_slice(&[0.0, 180.0], &[2, 1]);
3421        assert!(all_close(degrees(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3422    }
3423
3424    #[test]
3425    fn test_unary_radians() {
3426        let x = array![0.0];
3427        assert_eq!(radians(&x).unwrap().item_exact::<f32>(), 0.0);
3428
3429        let x = array![90.0];
3430        assert_eq!(
3431            radians(&x).unwrap().item_exact::<f32>(),
3432            std::f32::consts::PI / 2.0
3433        );
3434
3435        assert_array_eq(
3436            radians(array!()).unwrap(),
3437            array!(),
3438            tolerances::EXACT.rtol,
3439            tolerances::EXACT.atol,
3440        );
3441
3442        // Integer input type
3443        let x = array![90];
3444        assert_eq!(x.dtype(), Dtype::Int32);
3445        assert_eq!(
3446            radians(&x).unwrap().item_exact::<f32>(),
3447            std::f32::consts::PI / 2.0
3448        );
3449
3450        // Input is irregularly strided
3451        let x = broadcast_to(&array!(90.0), &[2, 2, 2]).unwrap();
3452        let res = radians(&x).unwrap();
3453        let expected = Array::full::<f32>(&[2, 2, 2], array!(std::f32::consts::PI / 2.0)).unwrap();
3454        assert!(all_close(&res, &expected, None, None, None).unwrap());
3455
3456        let angles = Array::from_slice(&[0.0, 90.0, 180.0, 270.0], &[2, 2]);
3457        let x = split_equal(&angles, 2, 1).unwrap();
3458        let expected = Array::from_slice(&[0.0, PI], &[2, 1]);
3459        assert!(all_close(radians(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3460    }
3461
3462    #[test]
3463    fn test_unary_log() {
3464        let x = array![0.0];
3465        assert_eq!(log(&x).unwrap().item_exact::<f32>(), NEG_INF);
3466
3467        let x = array![1.0];
3468        assert_eq!(log(&x).unwrap().item_exact::<f32>(), 0.0);
3469
3470        // Integer input type
3471        let x = array![1];
3472        assert_eq!(log(&x).unwrap().dtype(), Dtype::Float32);
3473        assert_eq!(log(&x).unwrap().item_exact::<f32>(), 0.0);
3474
3475        // Input is irregularly strided
3476        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3477        let res = log(&x).unwrap();
3478        let expected = Array::full::<f32>(&[2, 2, 2], array!(0.0)).unwrap();
3479        assert!(all_close(&res, &expected, None, None, None).unwrap());
3480
3481        let data = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3482        let x = split_equal(&data, 2, 1).unwrap();
3483        let expected = Array::from_slice(&[1.0f32.ln(), 3.0f32.ln()], &[2, 1]);
3484        assert!(all_close(log(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3485    }
3486
3487    #[test]
3488    fn test_unary_log2() {
3489        let x = array![0.0];
3490        assert_eq!(log2(&x).unwrap().item_exact::<f32>(), NEG_INF);
3491
3492        let x = array![1.0];
3493        assert_eq!(log2(&x).unwrap().item_exact::<f32>(), 0.0);
3494
3495        let x = array![1024.0];
3496        assert_eq!(log2(&x).unwrap().item_exact::<f32>(), 10.0);
3497    }
3498
3499    #[test]
3500    fn test_unary_log10() {
3501        let x = array![0.0];
3502        assert_eq!(log10(&x).unwrap().item_exact::<f32>(), NEG_INF);
3503
3504        let x = array![1.0];
3505        assert_eq!(log10(&x).unwrap().item_exact::<f32>(), 0.0);
3506
3507        let x = array![1000.0];
3508        assert_eq!(log10(&x).unwrap().item_exact::<f32>(), 3.0);
3509    }
3510
3511    #[test]
3512    fn test_unary_log1p() {
3513        let x = array![-1.0];
3514        assert_float_eq! {
3515            log1p(&x).unwrap().item_exact::<f32>(),
3516            (-1.0f32).ln_1p(),
3517            abs <= 1e-5
3518        };
3519
3520        let x = array![1.0];
3521        assert_float_eq! {
3522            log1p(&x).unwrap().item_exact::<f32>(),
3523            1.0f32.ln_1p(),
3524            abs <= 1e-5
3525        };
3526
3527        // Integer input type
3528        let x = array![1];
3529        assert_eq!(log1p(&x).unwrap().dtype(), Dtype::Float32);
3530        assert_float_eq! {
3531            log1p(&x).unwrap().item_exact::<f32>(),
3532            1.0f32.ln_1p(),
3533            abs <= 1e-5
3534        };
3535
3536        // Input is irregularly strided
3537        let x = broadcast_to(&array!(1.0), &[2, 2, 2]).unwrap();
3538        let res = log1p(&x).unwrap();
3539        let expected = Array::full::<f32>(&[2, 2, 2], array!(1.0f32.ln_1p())).unwrap();
3540        assert!(all_close(&res, &expected, None, None, None).unwrap());
3541
3542        let data = Array::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3543        let x = split_equal(&data, 2, 1).unwrap();
3544        let expected = Array::from_slice(&[1.0f32.ln_1p(), 3.0f32.ln_1p()], &[2, 1]);
3545        assert!(all_close(log1p(&x[0]).unwrap(), &expected, None, None, None).unwrap());
3546    }
3547
3548    #[test]
3549    fn test_unary_sigmoid() {
3550        let x = array![0.0];
3551        assert_float_eq! {
3552            sigmoid(&x).unwrap().item_exact::<f32>(),
3553            0.5,
3554            abs <= 1e-5
3555        };
3556
3557        // Integer input type
3558        let x = array![0];
3559        assert_eq!(sigmoid(&x).unwrap().dtype(), Dtype::Float32);
3560        assert_float_eq! {
3561            sigmoid(&x).unwrap().item_exact::<f32>(),
3562            0.5,
3563            abs <= 1e-5
3564        };
3565
3566        let inf = f32::INFINITY;
3567        let x = array![inf];
3568        assert_eq!(sigmoid(&x).unwrap().item_exact::<f32>(), 1.0);
3569
3570        let x = array![-inf];
3571        assert_eq!(sigmoid(&x).unwrap().item_exact::<f32>(), 0.0);
3572    }
3573
3574    #[test]
3575    fn test_unary_square() {
3576        let x = array![3.0];
3577        assert_eq!(square(&x).unwrap().item_exact::<f32>(), 9.0);
3578
3579        let x = array![2];
3580        assert_eq!(square(&x).unwrap().item_exact::<i32>(), 4);
3581
3582        let x = Array::full::<f32>(&[3, 3], array!(2.0)).unwrap();
3583        assert!(all_close(
3584            square(&x).unwrap(),
3585            Array::full::<f32>(&[3, 3], array!(4.0)).unwrap(),
3586            None,
3587            None,
3588            None
3589        )
3590        .unwrap());
3591    }
3592
3593    #[test]
3594    fn test_unary_sqrt_rsqrt() {
3595        let x = array![4.0];
3596        assert_eq!(sqrt(&x).unwrap().item_exact::<f32>(), 2.0);
3597        assert_eq!(rsqrt(&x).unwrap().item_exact::<f32>(), 0.5);
3598
3599        let x = Array::full::<f32>(&[3, 3], array!(9.0)).unwrap();
3600        assert!(all_close(
3601            sqrt(&x).unwrap(),
3602            Array::full::<f32>(&[3, 3], array!(3.0)).unwrap(),
3603            None,
3604            None,
3605            None
3606        )
3607        .unwrap());
3608
3609        let x = array![4i32];
3610        assert_eq!(sqrt(&x).unwrap().item_exact::<f32>(), 2.0);
3611        assert_eq!(rsqrt(&x).unwrap().item_exact::<f32>(), 0.5);
3612    }
3613
3614    #[test]
3615    fn test_unary_reciprocal() {
3616        let x = array![8.0];
3617        assert_eq!(reciprocal(&x).unwrap().item_exact::<f32>(), 0.125);
3618
3619        let x = array![2];
3620        let out = reciprocal(&x).unwrap();
3621        assert_eq!(out.dtype(), Dtype::Float32);
3622        assert_eq!(out.item_exact::<f32>(), 0.5);
3623
3624        let x = Array::full::<f32>(&[3, 3], array!(2.0)).unwrap();
3625        assert!(all_close(
3626            reciprocal(&x).unwrap(),
3627            Array::full::<f32>(&[3, 3], array!(0.5)).unwrap(),
3628            None,
3629            None,
3630            None
3631        )
3632        .unwrap());
3633    }
3634
3635    #[test]
3636    fn test_unary_real_imag() {
3637        let x = Array::from_complex(complex64::new(0.0, 1.0));
3638        assert_array_eq(
3639            real(&x).unwrap(),
3640            Array::from_f32(0.0),
3641            tolerances::EXACT.rtol,
3642            tolerances::EXACT.atol,
3643        );
3644        assert_array_eq(
3645            imag(&x).unwrap(),
3646            Array::from_f32(1.0),
3647            tolerances::EXACT.rtol,
3648            tolerances::EXACT.atol,
3649        );
3650    }
3651
3652    #[test]
3653    fn test_binary_add() {
3654        let x = array![1.0];
3655        let y = array![1.0];
3656        let z = add(&x, &y).unwrap();
3657        assert_eq!(z.item_exact::<f32>(), 2.0);
3658
3659        let z = &x + y;
3660        assert_eq!(z.item_exact::<f32>(), 2.0);
3661
3662        let z = add(z, &x).unwrap();
3663        assert_eq!(z.item_exact::<f32>(), 3.0);
3664
3665        // Chain a few adds:
3666        let mut out = x.deep_clone();
3667        for _ in 0..10 {
3668            out = add(&out, &x).unwrap();
3669        }
3670        assert_eq!(out.item_exact::<f32>(), 11.0);
3671
3672        // Works for different shapes
3673        let x = array!([1.0, 2.0, 3.0]);
3674        let y = array!([1.0, 2.0, 3.0]);
3675        let z = add(&x, &y).unwrap();
3676        assert_eq!(z.shape(), &[3]);
3677        assert_array_eq(
3678            z,
3679            array!([2.0, 4.0, 6.0]),
3680            tolerances::EXACT.rtol,
3681            tolerances::EXACT.atol,
3682        );
3683
3684        // Works with scalars
3685        let x = array!([1.0, 2.0, 3.0]);
3686        let y = &x + 2.0;
3687        assert_eq!(y.dtype(), Dtype::Float32);
3688        assert_array_eq(
3689            y,
3690            array!([3.0, 4.0, 5.0]),
3691            tolerances::EXACT.rtol,
3692            tolerances::EXACT.atol,
3693        );
3694        let y = &x + 2.0;
3695        assert_eq!(y.dtype(), Dtype::Float32);
3696        assert_array_eq(
3697            y,
3698            array!([3.0, 4.0, 5.0]),
3699            tolerances::EXACT.rtol,
3700            tolerances::EXACT.atol,
3701        );
3702
3703        // Check type promotion
3704        let y = x + 2;
3705        assert_eq!(y.dtype(), Dtype::Float32);
3706
3707        let y = array!([1, 2, 3]) + 2.0;
3708        assert_eq!(y.dtype(), Dtype::Float32);
3709        // assert!(array_equal(&y, &array![3.0, 4.0, 5.0]).item_exact::<bool>());
3710        assert_array_eq(
3711            y,
3712            array!([3.0, 4.0, 5.0]),
3713            tolerances::EXACT.rtol,
3714            tolerances::EXACT.atol,
3715        );
3716
3717        // Broadcasting works
3718        let x = broadcast_to(&array!(1.0), &[10]).unwrap();
3719        let y = broadcast_to(&array!(2.0), &[10]).unwrap();
3720        let z = add(&x, &y).unwrap();
3721        assert_array_eq(
3722            z,
3723            full::<f32>(&[10], array!(3.0)).unwrap(),
3724            tolerances::EXACT.rtol,
3725            tolerances::EXACT.atol,
3726        );
3727
3728        let x = Array::from_slice(&[1.0, 2.0], &[1, 2]);
3729        let y = Array::from_slice(&[1.0, 2.0], &[2, 1]);
3730        let z = add(&x, &y).unwrap();
3731        assert_eq!(z.shape(), &[2, 2]);
3732        assert_array_eq(
3733            z,
3734            Array::from_slice(&[2.0, 3.0, 3.0, 4.0], &[2, 2]),
3735            tolerances::EXACT.rtol,
3736            tolerances::EXACT.atol,
3737        );
3738
3739        let x = ones::<f32>(&[3, 2, 1]).unwrap();
3740        let z = x + 2.0;
3741        assert_eq!(z.shape(), &[3, 2, 1]);
3742        let expected = Array::from_slice(&[3.0, 3.0, 3.0, 3.0, 3.0, 3.0], &[3, 2, 1]);
3743        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
3744
3745        // Works for empty arrays
3746        let x = array!();
3747        let y = array!();
3748        let z = x + y;
3749        z.eval().unwrap();
3750        assert_eq!(z.size(), 0);
3751        assert_eq!(z.shape(), &[0]);
3752    }
3753
3754    #[test]
3755    fn test_binary_sub() {
3756        let x = array!([3.0, 2.0, 1.0]);
3757        let y = array!([1.0, 1.0, 1.0]);
3758        assert_array_eq(
3759            x - y,
3760            array!([2.0, 1.0, 0.0]),
3761            tolerances::EXACT.rtol,
3762            tolerances::EXACT.atol,
3763        );
3764    }
3765
3766    #[test]
3767    fn test_binary_mul() {
3768        let x = array!([1.0, 2.0, 3.0]);
3769        let y = array!([2.0, 2.0, 2.0]);
3770        assert_array_eq(
3771            x * y,
3772            array!([2.0, 4.0, 6.0]),
3773            tolerances::EXACT.rtol,
3774            tolerances::EXACT.atol,
3775        );
3776    }
3777
3778    #[test]
3779    fn test_binary_div() {
3780        let x = array![1.0];
3781        let y = array![1.0];
3782        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3783
3784        let x = array![1.0];
3785        let y = array![0.5];
3786        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 2.0);
3787
3788        let x = array![1.0];
3789        let y = array![4.0];
3790        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 0.25);
3791
3792        let x = array![true];
3793        let y = array![true];
3794        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3795
3796        let x = array![false];
3797        let y = array![true];
3798        assert_eq!(divide(&x, &y).unwrap().item_exact::<f32>(), 0.0);
3799
3800        let x = array![true];
3801        let y = array![false];
3802        assert!(divide(&x, &y).unwrap().item_exact::<f32>().is_infinite());
3803
3804        let x = array![false];
3805        let y = array![false];
3806        assert!(divide(&x, &y).unwrap().item_exact::<f32>().is_nan());
3807    }
3808
3809    #[test]
3810    fn test_binary_maximum_minimum() {
3811        let x = array![1.0];
3812        let y = array![0.0];
3813        assert_eq!(maximum(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3814        assert_eq!(minimum(&x, &y).unwrap().item_exact::<f32>(), 0.0);
3815
3816        let y = array![2.0];
3817        assert_eq!(maximum(&x, &y).unwrap().item_exact::<f32>(), 2.0);
3818        assert_eq!(minimum(&x, &y).unwrap().item_exact::<f32>(), 1.0);
3819    }
3820
3821    #[test]
3822    fn test_binary_logaddexp() {
3823        let x = array![0.0];
3824        let y = array![0.0];
3825        assert_float_eq! {
3826            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3827            2.0f32.ln(),
3828            abs <= 1e-5
3829        };
3830
3831        let x = array!([0u32]);
3832        let y = array!([10000u32]);
3833        assert_eq!(logaddexp(&x, &y).unwrap().item_exact::<f32>(), 10000.0);
3834
3835        let x = array![f32::INFINITY];
3836        let y = array![3.0];
3837        assert_eq!(
3838            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3839            f32::INFINITY
3840        );
3841
3842        let x = array![f32::NEG_INFINITY];
3843        let y = array![3.0];
3844        assert_eq!(logaddexp(&x, &y).unwrap().item_exact::<f32>(), 3.0);
3845
3846        let x = array![f32::NEG_INFINITY];
3847        let y = array![f32::NEG_INFINITY];
3848        assert_eq!(
3849            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3850            f32::NEG_INFINITY
3851        );
3852
3853        let x = array![f32::INFINITY];
3854        let y = array![f32::INFINITY];
3855        assert_eq!(
3856            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3857            f32::INFINITY
3858        );
3859
3860        let x = array![f32::NEG_INFINITY];
3861        let y = array![f32::INFINITY];
3862        assert_eq!(
3863            logaddexp(&x, &y).unwrap().item_exact::<f32>(),
3864            f32::INFINITY
3865        );
3866    }
3867
3868    #[test]
3869    fn test_basic_clip() {
3870        let a = array!([1.0, 4.0, 3.0, 8.0, 5.0]);
3871        let expected = array!([2.0, 4.0, 3.0, 6.0, 5.0]);
3872        let clipped = clip(&a, (array!(2.0), array!(6.0))).unwrap();
3873        assert_array_eq(
3874            clipped,
3875            &expected,
3876            tolerances::EXACT.rtol,
3877            tolerances::EXACT.atol,
3878        );
3879
3880        // Test with scalar
3881        let clipped = clip(&a, (2.0, 6.0)).unwrap();
3882        assert_array_eq(
3883            clipped,
3884            &expected,
3885            tolerances::EXACT.rtol,
3886            tolerances::EXACT.atol,
3887        );
3888    }
3889
3890    #[test]
3891    fn test_clip_with_only_min() {
3892        let a = array!([-1.0, 1.0, 0.0, 5.0]);
3893        let expected = array!([0.0, 1.0, 0.0, 5.0]);
3894        let clipped = clip(&a, (array!(0.0), ())).unwrap();
3895        assert_array_eq(
3896            clipped,
3897            &expected,
3898            tolerances::EXACT.rtol,
3899            tolerances::EXACT.atol,
3900        );
3901
3902        // Test with scalar
3903        let clipped = clip(&a, (0.0, ())).unwrap();
3904        assert_array_eq(
3905            clipped,
3906            expected,
3907            tolerances::EXACT.rtol,
3908            tolerances::EXACT.atol,
3909        );
3910    }
3911
3912    #[test]
3913    fn test_clip_with_only_max() {
3914        let a = array!([2.0, 3.0, 4.0, 5.0]);
3915        let expected = array!([2.0, 3.0, 4.0, 4.0]);
3916        let clipped = clip(&a, ((), array!(4.0))).unwrap();
3917        assert_array_eq(
3918            clipped,
3919            &expected,
3920            tolerances::EXACT.rtol,
3921            tolerances::EXACT.atol,
3922        );
3923
3924        // Test with scalar
3925        let clipped = clip(&a, ((), 4.0)).unwrap();
3926        assert_array_eq(
3927            clipped,
3928            expected,
3929            tolerances::EXACT.rtol,
3930            tolerances::EXACT.atol,
3931        );
3932    }
3933
3934    #[test]
3935    fn test_tensordot() {
3936        let x = reshape(arange::<_, f32>(None, 60.0, None).unwrap(), &[3, 4, 5]).unwrap();
3937        let y = reshape(arange::<_, f32>(None, 24.0, None).unwrap(), &[4, 3, 2]).unwrap();
3938        let z = tensordot_axes(&x, &y, &[1i32, 0], &[0i32, 1]).unwrap();
3939        let expected = Array::from_slice(
3940            &[
3941                4400.0_f32, 4730.0, 4532.0, 4874.0, 4664.0, 5018.0, 4796.0, 5162.0, 4928.0, 5306.0,
3942            ],
3943            &[5, 2],
3944        );
3945        assert_array_eq_with_context(
3946            z,
3947            expected,
3948            tolerances::EXACT.rtol,
3949            tolerances::EXACT.atol,
3950            "float32 explicit-axis contraction",
3951        );
3952
3953        let x = reshape(arange::<_, f32>(None, 360.0, None).unwrap(), &[3, 4, 5, 6]).unwrap();
3954        let y = reshape(arange::<_, f32>(None, 360.0, None).unwrap(), &[6, 4, 5, 3]).unwrap();
3955        assert!(tensordot_axes(&x, &y, &[2, 1, 3], &[1, 2, 0]).is_err());
3956
3957        let x = reshape(arange::<_, f32>(None, 60.0, None).unwrap(), &[3, 4, 5]).unwrap();
3958        let y = reshape(arange::<_, f32>(None, 120.0, None).unwrap(), &[4, 5, 6]).unwrap();
3959
3960        let z = tensordot_axis(&x, &y, 2).unwrap();
3961        let expected = Array::from_slice(
3962            &[
3963                14820.0, 15010.0, 15200.0, 15390.0, 15580.0, 15770.0, 37620.0, 38210.0, 38800.0,
3964                39390.0, 39980.0, 40570.0, 60420.0, 61410.0, 62400.0, 63390.0, 64380.0, 65370.0,
3965            ],
3966            &[3, 6],
3967        );
3968        assert_array_eq_with_context(
3969            z,
3970            expected,
3971            tolerances::EXACT.rtol,
3972            tolerances::EXACT.atol,
3973            "float32 axis-count contraction",
3974        );
3975    }
3976
3977    #[test]
3978    fn test_outer() {
3979        let x = arange::<_, f32>(1.0, 5.0, None).unwrap();
3980        let y = arange::<_, f32>(1.0, 4.0, None).unwrap();
3981        let z = outer(&x, &y).unwrap();
3982        let expected = Array::from_slice(
3983            &[1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0, 4.0, 8.0, 12.0],
3984            &[4, 3],
3985        );
3986        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
3987
3988        let x = ones::<f32>(&[5]).unwrap();
3989        let y = linspace::<_, f32>(
3990            -2.0,
3991            2.0,
3992            crate::ops::LinspaceOptions {
3993                count: 5,
3994                endpoint: true,
3995            },
3996        )
3997        .unwrap();
3998        let z = outer(&x, &y).unwrap();
3999        let expected = Array::from_slice(
4000            &[
4001                -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,
4002                -2.0, -1.0, 0.0, 1.0, 2.0, -2.0, -1.0, 0.0, 1.0, 2.0,
4003            ],
4004            &[5, 5],
4005        );
4006        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4007    }
4008
4009    #[test]
4010    fn test_inner() {
4011        let x = reshape(arange::<_, f32>(None, 5.0, None).unwrap(), &[1, 5]).unwrap();
4012        let y = reshape(arange::<_, f32>(None, 6.0, None).unwrap(), &[2, 3]).unwrap();
4013        assert!(inner(&x, &y).is_err());
4014
4015        let x = array!([1.0, 2.0, 3.0]);
4016        let y = array!([0.0, 1.0, 0.0]);
4017        let z = inner(&x, &y).unwrap();
4018        assert_eq!(z.item_exact::<f32>(), 2.0);
4019
4020        let x = reshape(arange::<_, f32>(None, 24.0, None).unwrap(), &[2, 3, 4]).unwrap();
4021        let y = arange::<_, f32>(None, 4.0, None).unwrap();
4022        let z = inner(&x, &y).unwrap();
4023        let expected = Array::from_slice(&[14.0, 38.0, 62.0, 86.0, 110.0, 134.0], &[2, 3]);
4024        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4025
4026        let x = reshape(arange::<_, f32>(None, 2.0, None).unwrap(), &[1, 1, 2]).unwrap();
4027        let y = reshape(arange::<_, f32>(None, 6.0, None).unwrap(), &[3, 2]).unwrap();
4028        let z = inner(&x, &y).unwrap();
4029        let expected = Array::from_slice(&[1.0, 3.0, 5.0], &[1, 1, 3]);
4030        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4031
4032        let x = eye::<f32>(2, None, None).unwrap();
4033        let y = Array::from_f32(7.0);
4034        let z = inner(&x, &y).unwrap();
4035        let expected = Array::from_slice(&[7.0, 0.0, 0.0, 7.0], &[2, 2]);
4036        assert_array_eq(z, expected, tolerances::EXACT.rtol, tolerances::EXACT.atol);
4037    }
4038
4039    #[test]
4040    fn test_divmod() {
4041        let x = array!([1.0, 2.0, 3.0]);
4042        let y = array!([1.0, 1.0, 1.0]);
4043        let out = divmod(&x, &y).unwrap();
4044        assert_array_eq(
4045            out.0,
4046            array!([1.0, 2.0, 3.0]),
4047            tolerances::EXACT.rtol,
4048            tolerances::EXACT.atol,
4049        );
4050        assert_array_eq(
4051            out.1,
4052            array!([0.0, 0.0, 0.0]),
4053            tolerances::EXACT.rtol,
4054            tolerances::EXACT.atol,
4055        );
4056
4057        let x = array!([5.0, 6.0, 7.0]);
4058        let y = array!([2.0, 2.0, 2.0]);
4059        let out = divmod(&x, &y).unwrap();
4060        assert_array_eq(
4061            out.0,
4062            array!([2.0, 3.0, 3.0]),
4063            tolerances::EXACT.rtol,
4064            tolerances::EXACT.atol,
4065        );
4066        assert_array_eq(
4067            out.1,
4068            array!([1.0, 0.0, 1.0]),
4069            tolerances::EXACT.rtol,
4070            tolerances::EXACT.atol,
4071        );
4072
4073        let x = array!([5.0, 6.0, 7.0]);
4074        let y = array!([2.0, 2.0, 2.0]);
4075        let out = divmod(&x, &y).unwrap();
4076        assert_array_eq(
4077            out.0,
4078            array!([2.0, 3.0, 3.0]),
4079            tolerances::EXACT.rtol,
4080            tolerances::EXACT.atol,
4081        );
4082        assert_array_eq(
4083            out.1,
4084            array!([1.0, 0.0, 1.0]),
4085            tolerances::EXACT.rtol,
4086            tolerances::EXACT.atol,
4087        );
4088
4089        let x = array![complex64::new(1.0, 0.0)];
4090        let y = array![complex64::new(2.0, 0.0)];
4091        assert!(divmod(&x, &y).is_err());
4092
4093        // Check that we can eval on both outputs
4094        let x = array![1.0];
4095        let y = array![2.0];
4096        let (quo, rem) = divmod(&x, &y).unwrap();
4097        eval([&quo, &rem]).unwrap();
4098        assert_eq!(quo.item_exact::<f32>(), 0.0);
4099        assert_eq!(rem.item_exact::<f32>(), 1.0);
4100
4101        // Check nested in the graph
4102        let x = array![1.0];
4103        let y = array![2.0];
4104        let (quo, rem) = divmod(&x, &y).unwrap();
4105        let z = quo + rem;
4106        assert_eq!(z.item_exact::<f32>(), 1.0);
4107
4108        // Check that we can still eval when one output goes out of scope
4109        let mut out_holder = {
4110            let (quo, _) = divmod(&x, &y).unwrap();
4111            vec![quo]
4112        };
4113        eval(out_holder.iter()).unwrap();
4114        assert_eq!(out_holder[0].item_exact::<f32>(), 0.0);
4115
4116        // Check that we can still eval when the other output goes out of scope
4117        out_holder.clear();
4118        let out_holder = {
4119            let (_, rem) = divmod(&x, &y).unwrap();
4120            vec![rem]
4121        };
4122        eval(out_holder.iter()).unwrap();
4123        assert_eq!(out_holder[0].item_exact::<f32>(), 1.0);
4124    }
4125
4126    // The tests below are adapted from the python unit test `test_blas.py/test_segmented_mm`
4127    #[test]
4128    fn test_segmented_mm() {
4129        use crate::ops::{indexing::*, stack};
4130        use crate::random;
4131
4132        // Reference implementation: for each segment [s1, s2], compute a[:, s1:s2] @ b[s1:s2, :]
4133        fn segmented_mm_ref(a: &Array, b: &Array, segments: &Array) -> Array {
4134            let segments_data: Vec<Vec<u32>> = (0..segments.shape()[0])
4135                .map(|i| {
4136                    let row = segments.index(i);
4137                    vec![
4138                        row.index(0).item_exact::<u32>(),
4139                        row.index(1).item_exact::<u32>(),
4140                    ]
4141                })
4142                .collect();
4143
4144            let results: Vec<Array> = segments_data
4145                .iter()
4146                .map(|seg| {
4147                    let s1 = seg[0] as i32;
4148                    let s2 = seg[1] as i32;
4149                    let a_slice = a.index((.., s1..s2));
4150                    let b_slice = b.index((s1..s2, ..));
4151                    a_slice.matmul(&b_slice).unwrap()
4152                })
4153                .collect();
4154
4155            stack(&results, 0).unwrap()
4156        }
4157
4158        // Test shapes from Python test
4159        let shapes = [(10, 10, 10), (10, 10, 100), (100, 100, 100)];
4160
4161        // Segment patterns from Python test
4162        let all_segments: Vec<Vec<f32>> = vec![
4163            vec![0.0, 0.0, 1.0],
4164            vec![0.0, 0.5, 1.0],
4165            (0..10).map(|r| r as f32 / 9.0).collect(),
4166        ];
4167
4168        random::seed(42).unwrap();
4169
4170        for (m, n, k) in shapes {
4171            for s in &all_segments {
4172                // Build segments array from proportions
4173                let mut segments_vec: Vec<[u32; 2]> = Vec::new();
4174                for i in 0..s.len() - 1 {
4175                    let s1 = ((k as f32 * s[i]) as u32).min(k as u32 - 1);
4176                    let s2 = ((k as f32 * s[i + 1]) as u32).min(k as u32 - 1);
4177                    segments_vec.push([s1, s2]);
4178                }
4179                let segments_flat: Vec<u32> = segments_vec.iter().flat_map(|x| *x).collect();
4180                let segments = Array::from_slice(&segments_flat, &[segments_vec.len() as i32, 2]);
4181
4182                // Test a @ b
4183                let a = random::normal::<f32>(&[m, k], None, None, None).unwrap();
4184                let b = random::normal::<f32>(&[k, n], None, None, None).unwrap();
4185                let c1 = segmented_mm_ref(&a, &b, &segments);
4186                let c2 = segmented_mm(&a, &b, &segments).unwrap();
4187                assert!(
4188                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4189                    "segmented_mm failed for shape ({}, {}, {}) with segments {:?}",
4190                    m,
4191                    n,
4192                    k,
4193                    s
4194                );
4195
4196                // Test a.T @ b (transposed a)
4197                let a = random::normal::<f32>(&[k, m], None, None, None).unwrap();
4198                let b = random::normal::<f32>(&[k, n], None, None, None).unwrap();
4199                let a_t = a.t();
4200                let c1 = segmented_mm_ref(&a_t, &b, &segments);
4201                let c2 = segmented_mm(&a_t, &b, &segments).unwrap();
4202                assert!(
4203                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4204                    "segmented_mm with transposed a failed for shape ({}, {}, {})",
4205                    m,
4206                    n,
4207                    k
4208                );
4209
4210                // Test a @ b.T (transposed b)
4211                let a = random::normal::<f32>(&[m, k], None, None, None).unwrap();
4212                let b = random::normal::<f32>(&[n, k], None, None, None).unwrap();
4213                let b_t = b.t();
4214                let c1 = segmented_mm_ref(&a, &b_t, &segments);
4215                let c2 = segmented_mm(&a, &b_t, &segments).unwrap();
4216                assert!(
4217                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4218                    "segmented_mm with transposed b failed for shape ({}, {}, {})",
4219                    m,
4220                    n,
4221                    k
4222                );
4223
4224                // Test a.T @ b.T (both transposed)
4225                let a = random::normal::<f32>(&[k, m], None, None, None).unwrap();
4226                let b = random::normal::<f32>(&[n, k], None, None, None).unwrap();
4227                let a_t = a.t();
4228                let b_t = b.t();
4229                let c1 = segmented_mm_ref(&a_t, &b_t, &segments);
4230                let c2 = segmented_mm(&a_t, &b_t, &segments).unwrap();
4231                assert!(
4232                    c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4233                    "segmented_mm with both transposed failed for shape ({}, {}, {})",
4234                    m,
4235                    n,
4236                    k
4237                );
4238            }
4239        }
4240    }
4241
4242    #[test]
4243    fn test_segmented_mm_batched_error() {
4244        // Batched input should fail (matches Python test)
4245        let a = ones::<f32>(&[2, 10, 10]).unwrap();
4246        let segments = Array::from_slice(&[0u32, 5, 5, 10], &[2, 2]);
4247        let result = segmented_mm(&a, &a, &segments);
4248        assert!(
4249            result.is_err(),
4250            "segmented_mm should fail for batched input"
4251        );
4252    }
4253
4254    // Tests adapted from Python test `test_blas.py/test_gather_matmul`
4255    #[test]
4256    fn test_gather_mm() {
4257        use crate::ops::indexing::take_axis;
4258        use crate::random;
4259
4260        random::seed(0).unwrap();
4261
4262        // Reference implementation using take
4263        fn gather_mm_ref(
4264            a: &Array,
4265            b: &Array,
4266            lhs_indices: Option<&Array>,
4267            rhs_indices: Option<&Array>,
4268        ) -> Array {
4269            let a = a
4270                .reshape(&[-1, a.shape()[a.ndim() - 2], a.shape()[a.ndim() - 1]])
4271                .unwrap();
4272            let b = b
4273                .reshape(&[-1, b.shape()[b.ndim() - 2], b.shape()[b.ndim() - 1]])
4274                .unwrap();
4275
4276            let a_gathered = match lhs_indices {
4277                Some(idx) => take_axis(&a, idx, 0).unwrap(),
4278                None => a,
4279            };
4280            let b_gathered = match rhs_indices {
4281                Some(idx) => take_axis(&b, idx, 0).unwrap(),
4282                None => b,
4283            };
4284            a_gathered.matmul(&b_gathered).unwrap()
4285        }
4286
4287        // Test case 1: batch_A=(1,), lhs_indices=(0,), batch_B=(3,), rhs_indices=(2, 1)
4288        let a = random::normal::<f32>(&[1, 32, 32], None, None, None).unwrap();
4289        let b = random::normal::<f32>(&[3, 32, 32], None, None, None).unwrap();
4290        let lhs_indices = Array::from_slice(&[0u32], &[1]);
4291        let rhs_indices = Array::from_slice(&[2u32, 1], &[2]);
4292
4293        let out_ref = gather_mm_ref(&a, &b, Some(&lhs_indices), Some(&rhs_indices));
4294        let out_test = gather_mm(&a, &b, &lhs_indices, &rhs_indices, None).unwrap();
4295        assert!(
4296            out_ref.all_close(&out_test, 1e-5, 1e-5, None).unwrap(),
4297            "gather_mm test case 1 failed"
4298        );
4299
4300        // Test case 2: batch_A=(1,), lhs_indices=None, batch_B=(3,), rhs_indices=(2, 1)
4301        let out_ref = gather_mm_ref(&a, &b, None, Some(&rhs_indices));
4302        let out_test = gather_mm(&a, &b, None::<&Array>, &rhs_indices, None).unwrap();
4303        assert!(
4304            out_ref.all_close(&out_test, 1e-5, 1e-5, None).unwrap(),
4305            "gather_mm test case 2 failed"
4306        );
4307
4308        // Test case 3: batch_A=(5,), lhs_indices=(0, 2), batch_B=(3,), rhs_indices=(2, 1)
4309        let a = random::normal::<f32>(&[5, 32, 32], None, None, None).unwrap();
4310        let lhs_indices = Array::from_slice(&[0u32, 2], &[2]);
4311
4312        let out_ref = gather_mm_ref(&a, &b, Some(&lhs_indices), Some(&rhs_indices));
4313        let out_test = gather_mm(&a, &b, &lhs_indices, &rhs_indices, None).unwrap();
4314        assert!(
4315            out_ref.all_close(&out_test, 1e-5, 1e-5, None).unwrap(),
4316            "gather_mm test case 3 failed"
4317        );
4318    }
4319
4320    // Test adapted from Python test `test_blas.py/test_gather_mm_sorted`
4321    #[test]
4322    fn test_gather_mm_sorted() {
4323        use crate::ops::indexing::take_axis;
4324        use crate::ops::sort;
4325        use crate::random;
4326
4327        random::seed(0).unwrap();
4328
4329        // Reference implementation
4330        fn gather_mm_ref(a: &Array, b: &Array, rhs: &Array) -> Array {
4331            let b_gathered = take_axis(b, rhs, 0).unwrap();
4332            a.matmul(&b_gathered).unwrap()
4333        }
4334
4335        let a = random::normal::<f32>(&[100, 1, 100], None, None, None).unwrap();
4336        let b = random::normal::<f32>(&[8, 100, 100], None, None, None).unwrap();
4337        let rhs = sort(random::randint::<_, i32>(0, 8, &[100], None).unwrap()).unwrap();
4338
4339        let c1 = gather_mm_ref(&a, &b, &rhs);
4340        let c2 = gather_mm(&a, &b, None::<&Array>, &rhs, true).unwrap();
4341        assert!(
4342            c1.all_close(&c2, 1e-4, 1e-4, None).unwrap(),
4343            "gather_mm_sorted failed"
4344        );
4345    }
4346}