Skip to main content

mlx_rs/ops/
sort.rs

1//! Implements bindings for the sorting ops.
2
3use mlx_internal_macros::generate_macro;
4
5use crate::{error::Result, utils::guard::Guarded, Array, Stream};
6
7/// Insertion side for [`Array::search_sorted`].
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum SearchSide {
10    /// Insert before equal values.
11    #[default]
12    Left,
13
14    /// Insert after equal values.
15    Right,
16}
17
18impl SearchSide {
19    fn as_c_ptr(self) -> *const std::ffi::c_char {
20        match self {
21            Self::Left => c"left".as_ptr(),
22            Self::Right => c"right".as_ptr(),
23        }
24    }
25}
26
27impl Array {
28    /// Find insertion indices for `values` in this one-dimensional sequence.
29    ///
30    /// The result has the values' shape and `u32` dtype. Sortedness is not validated; results for
31    /// unsorted sequences are unspecified.
32    ///
33    /// ```rust
34    /// use mlx_rs::{array, ops::SearchSide, Dtype};
35    ///
36    /// let result = array!([1, 2, 2, 3])
37    ///     .search_sorted(array!([2]), SearchSide::Right)
38    ///     .unwrap();
39    /// assert_eq!(result.dtype(), Dtype::Uint32);
40    /// ```
41    pub fn search_sorted(&self, values: impl AsRef<Array>, side: SearchSide) -> Result<Array> {
42        let stream = Stream::thread_local_or_default();
43        Array::try_from_op(|res| unsafe {
44            mlx_sys::mlx_searchsorted(
45                res,
46                self.as_ptr(),
47                values.as_ref().as_ptr(),
48                side.as_c_ptr(),
49                stream.as_ref().as_ptr(),
50            )
51        })
52    }
53}
54
55/// Returns a sorted copy of the array. Returns an error if the arguments are invalid.
56///
57/// # Params
58///
59/// - `array`: input array
60/// - `axis`: axis to sort over
61///
62/// # Example
63///
64/// ```rust
65/// use mlx_rs::{Array, ops::*};
66///
67/// let a = Array::from_slice(&[3, 2, 1], &[3]);
68/// let axis = 0;
69/// let result = sort_axis(&a, axis);
70/// ```
71pub fn sort_axis(a: impl AsRef<Array>, axis: i32) -> Result<Array> {
72    let stream = Stream::thread_local_or_default();
73    Array::try_from_op(|res| unsafe {
74        mlx_sys::mlx_sort_axis(res, a.as_ref().as_ptr(), axis, stream.as_ref().as_ptr())
75    })
76}
77
78/// Compatibility shim for [`sort_axis`].
79#[generate_macro(customize(forwarding_shim = true))]
80#[deprecated(
81    since = "0.26.0",
82    note = "use `with_stream` or `with_device` around `sort_axis`"
83)]
84pub fn sort_axis_device(
85    a: impl AsRef<Array>,
86    axis: i32,
87    #[optional] stream: impl AsRef<Stream>,
88) -> Result<Array> {
89    crate::with_stream(stream.as_ref(), || sort_axis(a, axis))
90}
91
92/// Returns a sorted copy of the flattened array. Returns an error if the arguments are invalid.
93///
94/// # Params
95///
96/// - `array`: input array
97///
98/// # Example
99///
100/// ```rust
101/// use mlx_rs::{Array, ops::*};
102///
103/// let a = Array::from_slice(&[3, 2, 1], &[3]);
104/// let result = sort(&a);
105/// ```
106pub fn sort(a: impl AsRef<Array>) -> Result<Array> {
107    let stream = Stream::thread_local_or_default();
108    Array::try_from_op(|res| unsafe {
109        mlx_sys::mlx_sort(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
110    })
111}
112
113/// Compatibility shim for [`sort`].
114#[generate_macro(customize(forwarding_shim = true))]
115#[deprecated(
116    since = "0.26.0",
117    note = "use `with_stream` or `with_device` around `sort`"
118)]
119pub fn sort_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
120    crate::with_stream(stream.as_ref(), || sort(a))
121}
122
123/// Returns the indices that sort the array. Returns an error if the arguments are invalid.
124///
125/// # Params
126///
127/// - `a`: The array to sort.
128/// - `axis`: axis to sort over
129///
130/// # Example
131///
132/// ```rust
133/// use mlx_rs::{Array, ops::*};
134///
135/// let a = Array::from_slice(&[3, 2, 1], &[3]);
136/// let axis = 0;
137/// let result = argsort_axis(&a, axis);
138/// ```
139pub fn argsort_axis(a: impl AsRef<Array>, axis: i32) -> Result<Array> {
140    let stream = Stream::thread_local_or_default();
141    Array::try_from_op(|res| unsafe {
142        mlx_sys::mlx_argsort_axis(res, a.as_ref().as_ptr(), axis, stream.as_ref().as_ptr())
143    })
144}
145
146/// Compatibility shim for [`argsort_axis`].
147#[generate_macro(customize(forwarding_shim = true))]
148#[deprecated(
149    since = "0.26.0",
150    note = "use `with_stream` or `with_device` around `argsort_axis`"
151)]
152pub fn argsort_axis_device(
153    a: impl AsRef<Array>,
154    axis: i32,
155    #[optional] stream: impl AsRef<Stream>,
156) -> Result<Array> {
157    crate::with_stream(stream.as_ref(), || argsort_axis(a, axis))
158}
159
160/// Returns the indices that sort the flattened array. Returns an error if the arguments are
161/// invalid.
162///
163/// # Params
164///
165/// - `a`: The array to sort.
166///
167/// # Example
168///
169/// ```rust
170/// use mlx_rs::{Array, ops::*};
171///
172/// let a = Array::from_slice(&[3, 2, 1], &[3]);
173/// let result = argsort(&a);
174/// ```
175pub fn argsort(a: impl AsRef<Array>) -> Result<Array> {
176    let stream = Stream::thread_local_or_default();
177    Array::try_from_op(|res| unsafe {
178        mlx_sys::mlx_argsort(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
179    })
180}
181
182/// Compatibility shim for [`argsort`].
183#[generate_macro(customize(forwarding_shim = true))]
184#[deprecated(
185    since = "0.26.0",
186    note = "use `with_stream` or `with_device` around `argsort`"
187)]
188pub fn argsort_device(
189    a: impl AsRef<Array>,
190    #[optional] stream: impl AsRef<Stream>,
191) -> Result<Array> {
192    crate::with_stream(stream.as_ref(), || argsort(a))
193}
194
195/// Returns a partitioned copy of the array such that the smaller `kth` elements are first.
196/// Returns an error if the arguments are invalid.
197///
198/// The ordering of the elements in partitions is undefined.
199///
200/// # Params
201///
202/// - `array`: input array
203/// - `kth`: Element at the `kth` index will be in its sorted position in the output. All elements
204///   before the kth index will be less or equal to the `kth` element and all elements after will be
205///   greater or equal to the `kth` element in the output.
206/// - `axis`: axis to partition over
207///
208/// # Example
209///
210/// ```rust
211/// use mlx_rs::{Array, ops::*};
212///
213/// let a = Array::from_slice(&[3, 2, 1], &[3]);
214/// let kth = 1;
215/// let axis = 0;
216/// let result = partition_axis(&a, kth, axis);
217/// ```
218pub fn partition_axis(a: impl AsRef<Array>, kth: i32, axis: i32) -> Result<Array> {
219    let stream = Stream::thread_local_or_default();
220    Array::try_from_op(|res| unsafe {
221        mlx_sys::mlx_partition_axis(
222            res,
223            a.as_ref().as_ptr(),
224            kth,
225            axis,
226            stream.as_ref().as_ptr(),
227        )
228    })
229}
230
231/// Compatibility shim for [`partition_axis`].
232#[generate_macro(customize(forwarding_shim = true))]
233#[deprecated(
234    since = "0.26.0",
235    note = "use `with_stream` or `with_device` around `partition_axis`"
236)]
237pub fn partition_axis_device(
238    a: impl AsRef<Array>,
239    kth: i32,
240    axis: i32,
241    #[optional] stream: impl AsRef<Stream>,
242) -> Result<Array> {
243    crate::with_stream(stream.as_ref(), || partition_axis(a, kth, axis))
244}
245
246/// Returns a partitioned copy of the flattened array such that the smaller `kth` elements are
247/// first. Returns an error if the arguments are invalid.
248///
249/// The ordering of the elements in partitions is undefined.
250///
251/// # Params
252///
253/// - `array`: input array
254/// - `kth`: Element at the `kth` index will be in its sorted position in the output. All elements
255///   before the kth index will be less or equal to the `kth` element and all elements after will be
256///   greater or equal to the `kth` element in the output.
257///
258/// # Example
259///
260/// ```rust
261/// use mlx_rs::{Array, ops::*};
262///
263/// let a = Array::from_slice(&[3, 2, 1], &[3]);
264/// let kth = 1;
265/// let result = partition(&a, kth);
266/// ```
267pub fn partition(a: impl AsRef<Array>, kth: i32) -> Result<Array> {
268    let stream = Stream::thread_local_or_default();
269    Array::try_from_op(|res| unsafe {
270        mlx_sys::mlx_partition(res, a.as_ref().as_ptr(), kth, stream.as_ref().as_ptr())
271    })
272}
273
274/// Compatibility shim for [`partition`].
275#[generate_macro(customize(forwarding_shim = true))]
276#[deprecated(
277    since = "0.26.0",
278    note = "use `with_stream` or `with_device` around `partition`"
279)]
280pub fn partition_device(
281    a: impl AsRef<Array>,
282    kth: i32,
283    #[optional] stream: impl AsRef<Stream>,
284) -> Result<Array> {
285    crate::with_stream(stream.as_ref(), || partition(a, kth))
286}
287
288/// Returns the indices that partition the array. Returns an error if the arguments are invalid.
289///
290/// The ordering of the elements within a partition in given by the indices is undefined.
291///
292/// # Params
293///
294/// - `a`: The array to sort.
295/// - `kth`: element index at the `kth` position in the output will give the sorted position.  All
296///   indices before the`kth` position will be of elements less than or equal to the element at the
297///   `kth` index and all indices after will be elemenents greater than or equal to the element at
298///   the `kth` position.
299/// - `axis`: axis to partition over
300///
301/// # Example
302///
303/// ```rust
304/// use mlx_rs::{Array, ops::*};
305///
306/// let a = Array::from_slice(&[3, 2, 1], &[3]);
307/// let kth = 1;
308/// let axis = 0;
309/// let result = argpartition_axis(&a, kth, axis);
310/// ```
311pub fn argpartition_axis(a: impl AsRef<Array>, kth: i32, axis: i32) -> Result<Array> {
312    let stream = Stream::thread_local_or_default();
313    Array::try_from_op(|res| unsafe {
314        mlx_sys::mlx_argpartition_axis(
315            res,
316            a.as_ref().as_ptr(),
317            kth,
318            axis,
319            stream.as_ref().as_ptr(),
320        )
321    })
322}
323
324/// Compatibility shim for [`argpartition_axis`].
325#[generate_macro(customize(forwarding_shim = true))]
326#[deprecated(
327    since = "0.26.0",
328    note = "use `with_stream` or `with_device` around `argpartition_axis`"
329)]
330pub fn argpartition_axis_device(
331    a: impl AsRef<Array>,
332    kth: i32,
333    axis: i32,
334    #[optional] stream: impl AsRef<Stream>,
335) -> Result<Array> {
336    crate::with_stream(stream.as_ref(), || argpartition_axis(a, kth, axis))
337}
338
339/// Returns the indices that partition the flattened array. Returns an error if the arguments are
340/// invalid.
341///
342/// The ordering of the elements within a partition in given by the indices is undefined.
343///
344/// # Params
345///
346/// - `a`: The array to sort.
347/// - `kth`: element index at the `kth` position in the output will give the sorted position.  All
348///   indices before the`kth` position will be of elements less than or equal to the element at the
349///   `kth` index and all indices after will be elemenents greater than or equal to the element at
350///   the `kth` position.
351///
352/// # Example
353///
354/// ```rust
355/// use mlx_rs::{Array, ops::*};
356///
357/// let a = Array::from_slice(&[3, 2, 1], &[3]);
358/// let kth = 1;
359/// let result = argpartition(&a, kth);
360/// ```
361pub fn argpartition(a: impl AsRef<Array>, kth: i32) -> Result<Array> {
362    let stream = Stream::thread_local_or_default();
363    Array::try_from_op(|res| unsafe {
364        mlx_sys::mlx_argpartition(res, a.as_ref().as_ptr(), kth, stream.as_ref().as_ptr())
365    })
366}
367
368/// Compatibility shim for [`argpartition`].
369#[generate_macro(customize(forwarding_shim = true))]
370#[deprecated(
371    since = "0.26.0",
372    note = "use `with_stream` or `with_device` around `argpartition`"
373)]
374pub fn argpartition_device(
375    a: impl AsRef<Array>,
376    kth: i32,
377    #[optional] stream: impl AsRef<Stream>,
378) -> Result<Array> {
379    crate::with_stream(stream.as_ref(), || argpartition(a, kth))
380}
381
382#[cfg(test)]
383mod tests {
384    use crate::Array;
385
386    #[test]
387    fn test_sort_with_invalid_axis() {
388        let a = Array::from_slice(&[1, 2, 3, 4, 5], &[5]);
389        let axis = 1;
390        let result = super::sort_axis(&a, axis);
391        assert!(result.is_err());
392    }
393
394    #[test]
395    fn test_partition_with_invalid_axis() {
396        let a = Array::from_slice(&[1, 2, 3, 4, 5], &[5]);
397        let kth = 2;
398        let axis = 1;
399        let result = super::partition_axis(&a, kth, axis);
400        assert!(result.is_err());
401    }
402
403    #[test]
404    fn test_partition_with_invalid_kth() {
405        let a = Array::from_slice(&[1, 2, 3, 4, 5], &[5]);
406        let kth = 5;
407        let axis = 0;
408        let result = super::partition_axis(&a, kth, axis);
409        assert!(result.is_err());
410    }
411
412    #[test]
413    fn test_partition_all_with_invalid_kth() {
414        let a = Array::from_slice(&[1, 2, 3, 4, 5], &[5]);
415        let kth = 5;
416        let result = super::partition(&a, kth);
417        assert!(result.is_err());
418    }
419}