Skip to main content

burn_tensor/tensor/api/
orderable.rs

1use burn_backend::{ElementConversion, Scalar};
2use burn_std::{AsIndex, IndexingUpdateOp};
3
4use crate::check::unwrap_dim_index;
5use crate::kind::Ordered;
6use crate::{Bool, Int, check};
7use crate::{Tensor, check::TensorCheck};
8
9impl<const D: usize, K> Tensor<D, K>
10where
11    K: Ordered,
12{
13    /// Sort the elements by value in ascending order along a given dimension.
14    ///
15    /// This sort is unstable (i.e., may reorder equal elements).
16    ///
17    /// # Arguments
18    ///
19    /// * `dim` - The dimension to sort along.
20    ///   Negative dimensions are supported and count from the end.
21    ///
22    /// # Returns
23    ///
24    /// A new tensor with the elements sorted in ascending order along the given dimension.
25    ///
26    /// # Example
27    ///
28    /// ```rust
29    /// use burn_tensor::{Tensor, Shape};
30    ///
31    /// let device = Default::default();
32    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
33    /// let sorted = tensor.clone().sort(0);
34    /// println!("{sorted}");
35    /// // [[5.0, -2.0, 3.0], [12.0, 3.0, 6.0]]
36    /// let sorted = tensor.sort(1);
37    /// println!("{sorted}");
38    /// // [[-2.0, 3.0, 12.0], [3.0, 5.0, 6.0]]
39    /// ```
40    pub fn sort<I: AsIndex>(self, dim: I) -> Self {
41        let dim = unwrap_dim_index(dim.try_dim_index(D), "Sort");
42        Tensor::new(K::sort(self.primitive, dim, /*descending*/ false))
43    }
44
45    /// Sort the elements by value in descending order along a given dimension.
46    ///
47    /// This sort is unstable (i.e., may reorder equal elements).
48    ///
49    /// # Arguments
50    ///
51    /// * `dim` - The dimension to sort along.
52    ///   Negative dimensions are supported and count from the end.
53    ///
54    /// # Returns
55    ///
56    /// A new tensor with the elements sorted in descending order along the given dimension.
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// use burn_tensor::{Tensor, Shape};
62    ///
63    /// let device = Default::default();
64    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
65    /// let sorted = tensor.clone().sort_descending(0);
66    /// println!("{sorted}");
67    /// // [[12.0, 3.0, 6.0], [5.0, -2.0, 3.0]]
68    /// let sorted = tensor.sort_descending(1);
69    /// println!("{sorted}");
70    /// // [[12.0, 3.0, -2.0], [6.0, 5.0, 3.0]]
71    /// ```
72    pub fn sort_descending<I: AsIndex>(self, dim: I) -> Self {
73        let dim = unwrap_dim_index(dim.try_dim_index(D), "Sort Descending");
74        Tensor::new(K::sort(self.primitive, dim, /*descending*/ true))
75    }
76
77    /// Sort the elements by value in ascending order along a given dimension.
78    /// Also returns the indices.
79    ///
80    /// This sort is unstable (i.e., may reorder equal elements).
81    ///
82    /// # Arguments
83    ///
84    /// * `dim` - The dimension to sort along.
85    ///   Negative dimensions are supported and count from the end.
86    ///
87    /// # Returns
88    ///
89    /// A tuple containing the sorted tensor and the indices tensor.
90    ///
91    /// # Example
92    ///
93    /// ```rust
94    /// use burn_tensor::{Tensor, Shape};
95    ///
96    /// let device = Default::default();
97    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
98    /// let (tensor, indices) = tensor.sort_with_indices(0);
99    /// println!("{tensor}");
100    /// // [[5.0, -2.0, 3.0], [12.0, 3.0, 6.0]]
101    /// println!("{}", indices);
102    /// // [[1, 0, 0], [0, 1, 1]]
103    /// ```
104    pub fn sort_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
105        let dim = unwrap_dim_index(dim.try_dim_index(D), "Sort With Indices");
106        let (values, indices) =
107            K::sort_with_indices(self.primitive, dim, /*descending*/ false);
108        (Tensor::new(values), Tensor::new(indices))
109    }
110
111    /// Sort the elements by value in descending order along a given dimension.
112    /// Also returns the indices.
113    ///
114    /// This sort is unstable (i.e., may reorder equal elements).
115    ///
116    /// # Arguments
117    ///
118    /// * `dim` - The dimension to sort along.
119    ///   Negative dimensions are supported and count from the end.
120    ///
121    /// # Example
122    ///
123    /// ```rust
124    /// use burn_tensor::{Tensor, Shape};
125    ///
126    /// let device = Default::default();
127    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
128    /// let (tensor, indices) = tensor.sort_descending_with_indices(0);
129    /// println!("{tensor}");
130    /// // [[12.0, 3.0, 6.0], [5.0, -2.0, 3.0]]
131    /// println!("{}", indices);
132    /// // [[0, 1, 1], [1, 0, 0]]
133    /// ```
134    pub fn sort_descending_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
135        let dim = unwrap_dim_index(dim.try_dim_index(D), "Sort Descending With Indices");
136        let (values, indices) = K::sort_with_indices(self.primitive, dim, /*descending*/ true);
137        (Tensor::new(values), Tensor::new(indices))
138    }
139
140    /// Returns the indices that sort the elements by value in ascending order along a given dimension.
141    ///
142    /// This sort is unstable (i.e., may reorder equal elements).
143    ///
144    /// # Arguments
145    ///
146    /// * `dim` - The dimension to sort along.
147    ///   Negative dimensions are supported and count from the end.
148    ///
149    /// # Example
150    ///
151    /// ```rust
152    /// use burn_tensor::{Tensor, Shape};
153    ///
154    /// let device = Default::default();
155    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
156    /// let tensor = tensor.argsort(0);
157    /// println!("{tensor}");
158    /// // [[1, 0, 0], [0, 1, 1]]
159    /// ```
160    pub fn argsort<I: AsIndex>(self, dim: I) -> Tensor<D, Int> {
161        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argsort");
162        Tensor::new(K::argsort(self.primitive, dim, /*descending*/ false))
163    }
164
165    /// Returns the indices that sort the elements by value in descending order along a given dimension.
166    ///
167    /// This sort is unstable (i.e., may reorder equal elements).
168    ///
169    /// # Arguments
170    ///
171    /// * `dim` - The dimension to sort along.
172    ///   Negative dimensions are supported and count from the end.
173    ///
174    /// # Example
175    ///
176    /// ```rust
177    /// use burn_tensor::{Tensor, Shape};
178    ///
179    /// let device = Default::default();
180    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
181    /// let indices = tensor.clone().argsort_descending(0);
182    /// println!("{indices}");
183    /// // [[0, 1, 1], [1, 0, 0]]
184    /// let indices = tensor.argsort_descending(1);
185    /// println!("{indices}");
186    /// // [[0, 2, 1], [2, 0, 1]]
187    /// ```
188    pub fn argsort_descending<I: AsIndex>(self, dim: I) -> Tensor<D, Int> {
189        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argsort Descending");
190        Tensor::new(K::argsort(self.primitive, dim, /*descending*/ true))
191    }
192
193    /// Returns the `k` largest elements of the given input tensor along a given dimension.
194    ///
195    /// # Arguments
196    ///
197    /// * `k` - The number of elements to return.
198    /// * `dim` - The dimension to sort along.
199    ///   Negative dimensions are supported and count from the end.
200    ///
201    /// # Returns
202    ///
203    /// A new tensor with the `k` largest elements along the given dimension.
204    ///
205    /// With autodiff, gradients are propagated to the input positions selected during the forward
206    /// pass. All other input positions receive zero gradient. When values are tied, the gradient
207    /// follows the indices selected by the backend.
208    ///
209    /// # Example
210    ///
211    /// ```rust
212    /// use burn_tensor::{Tensor, Shape};
213    ///
214    /// let device = Default::default();
215    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
216    /// let topk = tensor.clone().topk(2, 0);
217    /// println!("{topk}");
218    /// // [[12.0, 3.0, 6.0], [5.0, -2.0, 3.0]]
219    /// let topk = tensor.topk(1, 1);
220    /// println!("{topk}");
221    /// // [[12.0], [6.0]]
222    /// ```
223    pub fn topk<I: AsIndex>(self, k: usize, dim: I) -> Self {
224        let dim = unwrap_dim_index(dim.try_dim_index(D), "Top K");
225        check!(TensorCheck::topk("Top K", k, dim, &self.shape()));
226        Tensor::new(K::topk(self.primitive, dim, k))
227    }
228
229    /// Returns the `k` largest elements of the given input tensor along a given dimension.
230    /// Also returns the indices.
231    ///
232    /// # Arguments
233    ///
234    /// * `k` - The number of elements to return.
235    /// * `dim` - The dimension to sort along.
236    ///   Negative dimensions are supported and count from the end.
237    ///
238    /// With autodiff, gradients from the returned values are propagated to the input positions
239    /// identified by the returned indices. All other input positions receive zero gradient.
240    ///
241    /// # Example
242    ///
243    /// ```rust
244    /// use burn_tensor::{Tensor, Shape};
245    ///
246    /// let device = Default::default();
247    /// let tensor = Tensor::<2>::from_data([[12.0, -2.0, 3.0], [5.0, 3.0, 6.0]], &device);
248    /// let (tensor, indices) = tensor.topk_with_indices(2, 0);
249    /// println!("{tensor}");
250    /// // [[12.0, 3.0, 6.0], [5.0, -2.0, 3.0]]
251    /// println!("{}", indices);
252    /// // [[0, 1, 1], [1, 0, 0]]
253    /// let (tensor, indices) = tensor.topk_with_indices(1, 1);
254    /// println!("{tensor}");
255    /// // [[12.0], [6.0]]
256    /// println!("{indices}");
257    /// // [[0], [2]]
258    /// ```
259    pub fn topk_with_indices<I: AsIndex>(self, k: usize, dim: I) -> (Self, Tensor<D, Int>) {
260        let dim = unwrap_dim_index(dim.try_dim_index(D), "Top K With Indices");
261        check!(TensorCheck::topk(
262            "Top K With Indices",
263            k,
264            dim,
265            &self.shape()
266        ));
267        let (values, indices) = K::topk_with_indices(self.primitive, dim, k);
268        (Tensor::new(values), Tensor::new(indices))
269    }
270
271    /// Create a one hot tensor.
272    ///
273    /// # Example
274    ///
275    /// ```rust
276    /// use burn_tensor::Tensor;
277    ///
278    /// fn example(){
279    ///     let device = Default::default();
280    ///     let indices: Tensor<1> = Tensor::from_floats([0.0, 1.0, 2.0, 3.0], &device);
281    ///     let one_hot: Tensor<2> = indices.one_hot(4);
282    ///     println!("{}", one_hot.to_data());
283    ///     // [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]]
284    /// }
285    /// ```
286    pub fn one_hot<const D2: usize>(self, num_classes: usize) -> Tensor<D2, K> {
287        check!(TensorCheck::one_hot_tensor(self.clone(), num_classes));
288        self.one_hot_fill(num_classes, 1.0, 0.0, -1)
289    }
290
291    /// Create a one-hot encoded tensor with configurable `num_classes`, `on_value`, `off_value`, and `axis` including high-ranked tensors.
292    ///
293    /// # Arguments
294    ///
295    /// * `num_classes`: The number of classes for the one-hot encoding, which defines the size of the one-hot dimension.
296    /// * `on_value`: The value to assign for active positions (corresponding to indices).
297    /// * `off_value`: The value to assign for inactive positions.
298    /// * `axis`: The axis along which the one-hot dimension is added.
299    ///   Negative dimensions are supported and count from the end.
300    ///
301    /// # Returns
302    ///
303    /// A tensor with one additional dimension for the one-hot encoding, where active positions are filled with `on_value` and others with `off_value`.
304    ///
305    /// # Example
306    /// ```rust
307    /// use burn_tensor::{Tensor, Float};
308    /// let device = Default::default();
309    /// let indices: Tensor<2, Float> = Tensor::from_floats([[0., 2.], [1., -1.]], &device);
310    /// // One-hot encoding
311    /// let tensor: Tensor<3, Float> = indices.one_hot_fill(3, 5.0.into(), 0.0.into(), -1);
312    /// println!("{tensor}");
313    /// // [[[5.0, 0.0, 0.0],
314    /// // [0.0, 0.0, 5.0]],
315    /// // [[0.0, 5.0, 0.0],
316    /// // [0.0, 0.0, 5.0]]]
317    /// ```
318    pub fn one_hot_fill<const D2: usize>(
319        self,
320        num_classes: usize,
321        on_value: f32,
322        off_value: f32,
323        axis: impl AsIndex,
324    ) -> Tensor<D2, K> {
325        check!(TensorCheck::one_hot_tensor_rank::<D, D2>());
326        let axis = unwrap_dim_index(axis.try_dim_index(D + 1), "One Hot");
327
328        // Initialize shape from the current tensor dimensions and prepare for modification
329        let mut shape = self.shape();
330        let device = self.device();
331
332        // Convert the input tensor to integer indices
333        let indices: Tensor<D, Int> = Tensor::from_data(self.to_data().convert::<i64>(), &device);
334        // Insert the new dimension for the one-hot representation
335        shape.insert(axis, num_classes);
336        // Adjust indices to valid range and handle invalid indices
337        let adjusted_indices = indices
338            .clone()
339            .mask_fill(self.clone().lower_scalar(0), num_classes as i64) // Handle negative indices
340            .add(indices.clone().mask_fill(self.clone().greater_scalar(0), 0)); // Handle positive indices
341
342        // Unsqueeze the indices tensor along the specified axis
343        let indices_unsqueezed: Tensor<D2, Int> = adjusted_indices.unsqueeze_dim(axis);
344
345        // Initialize the output tensor with the off_value
346        let output = Tensor::full(shape.clone(), off_value, &device);
347
348        // Prepare scatter tensor for on_value and off_value adjustments
349        let scatter_on_values = Tensor::full(indices_unsqueezed.shape(), on_value, &device)
350            - Tensor::full(indices_unsqueezed.shape(), off_value, &self.device());
351
352        // Scatter on_value at the appropriate indices to create the one-hot representation
353        output.scatter(
354            axis,
355            indices_unsqueezed,
356            scatter_on_values,
357            IndexingUpdateOp::Add,
358        )
359    }
360
361    /// Applies element wise greater comparison and returns a boolean tensor.
362    ///
363    /// # Panics
364    ///
365    /// If the two tensors don't have the same shape.
366    ///
367    /// # Example
368    ///
369    /// ```rust
370    /// use burn_tensor::{Tensor, Shape};
371    ///
372    /// let device = Default::default();
373    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
374    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
375    /// let tensor = tensor1.greater(tensor2);
376    /// println!("{tensor}");
377    /// // [[false, false, false], [true, true, true]]
378    /// ```
379    pub fn greater(self, other: Self) -> Tensor<D, Bool> {
380        check!(TensorCheck::binary_ops_ew("Greater", &self, &other));
381        Tensor::new(K::greater(self.primitive, other.primitive))
382    }
383
384    /// Applies element wise greater-equal comparison and returns a boolean tensor.
385    ///
386    /// # Panics
387    ///
388    /// If the two tensors don't have the same shape.
389    ///
390    /// # Example
391    ///
392    /// ```rust
393    /// use burn_tensor::{Tensor, Shape};
394    ///
395    /// let device = Default::default();
396    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
397    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
398    /// let tensor = tensor1.greater_equal(tensor2);
399    /// println!("{tensor}");
400    /// // [[true, false, false], [true, true, true]]
401    /// ```
402    pub fn greater_equal(self, other: Self) -> Tensor<D, Bool> {
403        check!(TensorCheck::binary_ops_ew("Greater_equal", &self, &other));
404        Tensor::new(K::greater_equal(self.primitive, other.primitive))
405    }
406
407    /// Applies element wise lower comparison and returns a boolean tensor.
408    ///
409    /// # Panics
410    ///
411    /// If the two tensors don't have the same shape.
412    ///
413    /// # Example
414    ///
415    /// ```rust
416    /// use burn_tensor::{Tensor, Shape};
417    ///
418    /// let device = Default::default();
419    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
420    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
421    /// let tensor = tensor1.lower(tensor2);
422    /// println!("{tensor}");
423    /// // [[false, true, true], [false, false, false]]
424    /// ```
425    pub fn lower(self, other: Self) -> Tensor<D, Bool> {
426        check!(TensorCheck::binary_ops_ew("Lower", &self, &other));
427        Tensor::new(K::lower(self.primitive, other.primitive))
428    }
429
430    /// Applies element wise lower-equal comparison and returns a boolean tensor.
431    ///
432    /// # Panics
433    ///
434    /// If the two tensors don't have the same shape.
435    ///
436    /// # Example
437    ///
438    /// ```rust
439    /// use burn_tensor::{Tensor, Shape};
440    ///
441    /// let device = Default::default();
442    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
443    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
444    /// let tensor = tensor1.lower_equal(tensor2);
445    /// println!("{tensor}");
446    /// // [[true, true, true], [false, false, false]]
447    /// ```
448    pub fn lower_equal(self, other: Self) -> Tensor<D, Bool> {
449        check!(TensorCheck::binary_ops_ew("Lower_equal", &self, &other));
450        Tensor::new(K::lower_equal(self.primitive, other.primitive))
451    }
452
453    /// Applies greater than `other` comparison and returns a boolean tensor.
454    ///
455    /// # Arguments
456    ///
457    /// * `other` - The scalar to compare.
458    ///
459    /// # Example
460    ///
461    /// ```rust
462    /// use burn_tensor::{Tensor, Shape};
463    ///
464    /// let device = Default::default();
465    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
466    /// let tensor = tensor.greater_scalar(3.0);
467    /// println!("{tensor}");
468    /// // [[false, false, true], [true, true, true]]
469    /// ```
470    pub fn greater_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
471        let other = Scalar::new(other, &self.dtype());
472        Tensor::new(K::greater_scalar(self.primitive, other))
473    }
474
475    /// Applies greater-equal than `other` comparison and returns a boolean tensor.
476    ///
477    /// # Arguments
478    ///
479    /// * `other` - The scalar to compare.
480    ///
481    /// # Example
482    ///
483    /// ```rust
484    /// use burn_tensor::{Tensor, Shape};
485    ///
486    /// let device = Default::default();
487    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
488    /// let tensor = tensor.greater_equal_scalar(3.0);
489    /// println!("{tensor}");
490    /// // [[false, false, true], [true, true, true]]
491    /// ```
492    pub fn greater_equal_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
493        let other = Scalar::new(other, &self.dtype());
494        Tensor::new(K::greater_equal_scalar(self.primitive, other))
495    }
496
497    /// Applies lower than `other` comparison and returns a boolean tensor.
498    ///
499    /// # Arguments
500    ///
501    /// * `other` - The scalar to compare.
502    ///
503    /// # Example
504    ///
505    /// ```rust
506    /// use burn_tensor::{Tensor, Shape};
507    ///
508    /// let device = Default::default();
509    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
510    /// let tensor = tensor.lower_scalar(3.0);
511    /// println!("{tensor}");
512    /// // [[true, true, false], [false, false, false]]
513    /// ```
514    pub fn lower_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
515        let other = Scalar::new(other, &self.dtype());
516        Tensor::new(K::lower_scalar(self.primitive, other))
517    }
518
519    /// Applies lower-equal than `other` comparison and returns a boolean tensor.
520    ///
521    /// # Arguments
522    ///
523    /// * `other` - The scalar to compare.
524    ///
525    /// # Example
526    ///
527    /// ```rust
528    /// use burn_tensor::{Tensor, Shape};
529    ///
530    /// let device = Default::default();
531    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
532    /// let tensor = tensor.lower_equal_scalar(3.0);
533    /// println!("{tensor}");
534    /// // [[true, true, true], [false, false, false]]
535    /// ```
536    pub fn lower_equal_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
537        let other = Scalar::new(other, &self.dtype());
538        Tensor::new(K::lower_equal_scalar(self.primitive, other))
539    }
540
541    /// Alias for [greater_scalar](Self::greater_scalar).
542    pub fn greater_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
543        self.greater_scalar(other)
544    }
545
546    /// Alias for [greater_equal_scalar](Self::greater_equal_scalar).
547    pub fn greater_equal_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
548        self.greater_equal_scalar(other)
549    }
550
551    /// Alias for [lower_scalar](Self::lower_scalar).
552    pub fn lower_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
553        self.lower_scalar(other)
554    }
555
556    /// Alias for [lower_equal_scalar](Self::lower_equal_scalar).
557    pub fn lower_equal_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
558        self.lower_equal_scalar(other)
559    }
560
561    /// Applies the argmax function along the given dimension and returns an integer tensor.
562    ///
563    /// # Arguments
564    ///
565    /// * `dim` - The dimension along which to find the maximum value.
566    ///   Negative dimensions are supported and count from the end.
567    ///
568    /// # NaN behavior
569    ///
570    /// For floating-point tensors, NaNs take precedence over non-NaN values. If a reduced slice
571    /// contains multiple NaNs, the lowest coordinate along `dim` is returned. Non-NaN ties also
572    /// return the lowest coordinate.
573    ///
574    /// # Example
575    ///
576    /// ```rust
577    /// use burn_tensor::{Tensor, Shape};
578    ///
579    /// let device = Default::default();
580    /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 3]), &device);
581    /// let tensor = tensor.argmax(1);
582    /// println!("{:?}", tensor.shape());
583    /// // Shape { dims: [2, 1, 3] }
584    ///
585    /// let tensor = Tensor::<1>::from_data([3.0, f32::NAN, f32::NAN], &device);
586    /// let index: i32 = tensor.argmax(0).into_scalar();
587    /// assert_eq!(index, 1);
588    /// ```
589    pub fn argmax(self, dim: impl AsIndex) -> Tensor<D, Int> {
590        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argmax");
591        Tensor::new(K::argmax(self.primitive, dim))
592    }
593
594    /// Applies the argtopk function along the given dimension and returns an integer tensor.
595    ///
596    /// # Arguments
597    ///
598    /// * `k` - The number of indices to return.
599    /// * `dim` - The dimension along which to find the largest values.
600    ///   Negative dimensions are supported and count from the end.
601    ///
602    /// # Example
603    ///
604    /// ```rust
605    /// use burn_tensor::{Tensor, Shape};
606    ///
607    /// let device = Default::default();
608    /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 3]), &device);
609    /// let tensor = tensor.argtopk(1, 2);
610    /// println!("{:?}", tensor.shape());
611    /// ```
612    pub fn argtopk(self, k: usize, dim: impl AsIndex) -> Tensor<D, Int> {
613        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argtopk");
614        assert!(self.shape()[dim] > k);
615        Tensor::new(K::argtopk(self.primitive, dim, k))
616    }
617
618    /// Find the maximum value.
619    ///
620    /// For floating-point tensors, the result is NaN if any element is NaN.
621    ///
622    /// # Example
623    ///
624    /// ```rust
625    /// use burn_tensor::{Tensor, Shape};
626    ///
627    /// let device = Default::default();
628    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
629    /// let tensor = tensor.max();
630    /// println!("{tensor}");
631    /// // [9.0]
632    ///
633    /// let tensor = Tensor::<1>::from_data([1.0, f32::NAN, 3.0], &device);
634    /// let value: f32 = tensor.max().into_scalar();
635    /// assert!(value.is_nan());
636    /// ```
637    pub fn max(self) -> Tensor<1, K> {
638        Tensor::new(K::max(self.primitive))
639    }
640
641    /// Find the maximum value along the given dimension.
642    ///
643    /// Also returns the indices.
644    ///
645    /// For floating-point tensors, a NaN in a reduced slice produces a NaN value. The returned
646    /// index is the lowest coordinate containing NaN. Non-NaN ties also return the lowest
647    /// coordinate.
648    ///
649    /// # Example
650    ///
651    /// ```rust
652    /// use burn_tensor::{Tensor, Shape};
653    ///
654    /// let device = Default::default();
655    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
656    /// let (tensor, index) = tensor.max_dim_with_indices(0);
657    /// // [[5.0, 9.0, 6.0]]
658    /// println!("{tensor}");
659    /// // [[1, 1, 1]]
660    /// println!("{index}");
661    /// ```
662    pub fn max_dim_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
663        let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Dim With Indices");
664
665        let (tensor, index) = K::max_dim_with_indices(self.primitive, dim);
666
667        let tensor = Tensor::new(tensor);
668        let index = Tensor::new(index);
669
670        (tensor, index)
671    }
672
673    /// Find the maximum absolute value.
674    ///
675    /// For floating-point tensors, the result is NaN if any element is NaN.
676    ///
677    /// # Example
678    ///
679    /// ```rust
680    /// use burn_tensor::{Tensor, Shape};
681    ///
682    /// let device = Default::default();
683    /// let tensor = Tensor::<2>::from_data([[1.0, -7.0, 3.0], [5.0, -1.0, 6.0]], &device);
684    /// let tensor = tensor.max_abs();
685    /// println!("{tensor}");
686    /// // [7.0]
687    /// ```
688    pub fn max_abs(self) -> Tensor<1, K> {
689        Tensor::new(K::max_abs(self.primitive))
690    }
691
692    /// Finds the maximum pair wise values with another tensor.
693    ///
694    /// # Arguments
695    ///
696    /// * `other` - Other tensor to find maximum elements with
697    ///
698    /// # Returns
699    ///
700    /// A tensor with the same shape as the input tensors containing the maximum value found
701    /// in the input tensors.
702    ///
703    /// # Example
704    ///
705    /// ```rust
706    /// use burn_tensor::{Tensor, Shape};
707    ///
708    /// let device = Default::default();
709    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
710    /// let tensor2 = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
711    /// let tensor = tensor1.max_pair(tensor2);
712    /// println!("{tensor}");
713    /// // [[2.0, 3.0, 4.0], [5.0, 9.0, 6.0]]
714    /// ```
715    pub fn max_pair(self, other: Self) -> Self {
716        let mask = self.clone().lower(other.clone());
717        self.mask_where(mask, other)
718    }
719
720    /// Find the maximum absolute value along the given dimension.
721    ///
722    /// # Arguments
723    ///
724    /// * `dim` - The dimension or axis along which to aggregate the elements,
725    ///   supports negative indexing.
726    ///
727    /// # Returns
728    ///
729    /// The returned tensor will have the same rank,
730    /// but the aggregated dimension will have size 1.
731    ///
732    /// For floating-point tensors, a reduced slice produces NaN if it contains a NaN.
733    ///
734    /// # Example
735    ///
736    /// ```rust
737    /// use burn_tensor::{Tensor, Shape};
738    ///
739    /// let device = Default::default();
740    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
741    /// let tensor = tensor.max_dim(0);
742    /// println!("{tensor}");
743    /// // [[5.0, 9.0, 6.0]]
744    /// ```
745    pub fn max_abs_dim<I: AsIndex>(self, dim: I) -> Self {
746        let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Abs Dim");
747
748        Tensor::new(K::max_abs_dim(self.primitive, dim))
749    }
750
751    /// Find the maximum absolute value along the given dimensions.
752    ///
753    /// # Arguments
754    ///
755    /// * `dims` - The dimensions or axes along which to aggregate the elements,
756    ///   supports negative indexing.
757    ///
758    /// # Returns
759    ///
760    /// The returned tensor will have the same rank,
761    /// but the aggregated dimensions will have size 1.
762    ///
763    /// For floating-point tensors, a reduced region produces NaN if it contains a NaN.
764    ///
765    /// # Example
766    ///
767    /// ```rust
768    /// use burn_tensor::{Tensor, Shape};
769    ///
770    /// let device = Default::default();
771    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
772    /// let tensor = tensor.max_abs_dims(&[0, 1]);
773    /// println!("{tensor}");
774    /// // [[9.0]]
775    /// ```
776    ///
777    /// # Empty dimensions
778    ///
779    /// Unlike ordinary reductions such as [`Tensor::max_dims`], this is a composite
780    /// operation equivalent to `self.abs().max_dims(dims)`. If `dims` is empty, no
781    /// reduction is performed, but the elementwise absolute-value operation is
782    /// still applied.
783    pub fn max_abs_dims<I: AsIndex>(self, dims: &[I]) -> Self {
784        if dims.is_empty() {
785            return self.abs();
786        }
787        dims.iter()
788            .fold(self, |tensor, &dim| tensor.max_abs_dim(dim))
789    }
790
791    /// Applies the argmin function along the given dimension and returns an integer tensor.
792    ///
793    /// # Arguments
794    ///
795    /// * `dim` - The dimension along which to find the minimum value.
796    ///   Negative dimensions are supported and count from the end.
797    ///
798    /// # NaN behavior
799    ///
800    /// For floating-point tensors, NaNs take precedence over non-NaN values. If a reduced slice
801    /// contains multiple NaNs, the lowest coordinate along `dim` is returned. Non-NaN ties also
802    /// return the lowest coordinate.
803    ///
804    /// # Example
805    ///
806    /// ```rust
807    /// use burn_tensor::{Tensor, Shape};
808    ///
809    /// let device = Default::default();
810    /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 3]), &device);
811    /// let tensor = tensor.argmin(1);
812    /// println!("{:?}", tensor.shape());
813    /// // Shape { dims: [2, 1, 3] }
814    /// ```
815    pub fn argmin(self, dim: impl AsIndex) -> Tensor<D, Int> {
816        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argmin");
817        Tensor::new(K::argmin(self.primitive, dim))
818    }
819
820    /// Find the minimum value.
821    ///
822    /// For floating-point tensors, the result is NaN if any element is NaN.
823    ///
824    /// # Example
825    ///
826    /// ```rust
827    /// use burn_tensor::{Tensor, Shape};
828    ///
829    /// let device = Default::default();
830    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
831    /// let tensor = tensor.min();
832    /// println!("{tensor}");
833    /// // [-2.0]
834    /// ```
835    pub fn min(self) -> Tensor<1, K> {
836        Tensor::new(K::min(self.primitive))
837    }
838
839    /// Find the minimum value along the given dimension.
840    ///
841    /// # Arguments
842    ///
843    /// * `dim` - The dimension or axis along which to aggregate the elements;
844    ///   supports negative indexing.
845    ///
846    /// # Returns
847    ///
848    /// The returned tensor will have the same rank,
849    /// but the aggregated dimension will have size 1.
850    ///
851    /// For floating-point tensors, a reduced slice produces NaN if it contains a NaN.
852    ///
853    /// # Example
854    ///
855    /// ```rust
856    /// use burn_tensor::{Tensor, Shape};
857    ///
858    /// let device = Default::default();
859    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
860    /// let tensor = tensor.min_dim(0);
861    /// println!("{tensor}");
862    /// // [[1.0, -2.0, 3.0]]
863    /// ```
864    pub fn min_dim<I: AsIndex>(self, dim: I) -> Self {
865        let dim = unwrap_dim_index(dim.try_dim_index(D), "Min Dim");
866        Tensor::new(K::min_dim(self.primitive, dim))
867    }
868
869    /// Find the minimum value along the given dimensions.
870    ///
871    /// # Arguments
872    ///
873    /// * `dims` - The dimensions or axes along which to aggregate the elements;
874    ///   supports negative indexing.
875    ///
876    /// # Returns
877    ///
878    /// The returned tensor will have the same rank,
879    /// but the aggregated dimensions will have size 1.
880    ///
881    /// For floating-point tensors, a reduced region produces NaN if it contains a NaN.
882    ///
883    /// # Example
884    ///
885    /// ```rust
886    /// use burn_tensor::{Tensor, Shape};
887    ///
888    /// let device = Default::default();
889    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
890    /// let tensor = tensor.min_dims(&[0, 1]);
891    /// println!("{tensor}");
892    /// // [[-2.0]]
893    /// ```
894    pub fn min_dims<I: AsIndex>(self, dims: &[I]) -> Self {
895        dims.iter().fold(self, |tensor, &dim| tensor.min_dim(dim))
896    }
897
898    /// Find the minimum value along the given dimension.
899    ///
900    /// Also returns the indices.
901    ///
902    /// For floating-point tensors, a NaN in a reduced slice produces a NaN value. The returned
903    /// index is the lowest coordinate containing NaN. Non-NaN ties also return the lowest
904    /// coordinate.
905    ///
906    /// # Example
907    ///
908    /// ```rust
909    /// use burn_tensor::{Tensor, Shape};
910    ///
911    /// let device = Default::default();
912    /// let tensor = Tensor::<2>::from_data([[7.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
913    /// let (tensor, index) = tensor.min_dim_with_indices(0);
914    /// println!("{tensor}");
915    /// // [[5.0, -2.0, 3.0]]
916    /// println!("{}", index);
917    /// // [[1, 0, 0]]
918    /// ```
919    pub fn min_dim_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
920        let dim = unwrap_dim_index(dim.try_dim_index(D), "Min Dim With Indices");
921
922        let (tensor, index) = K::min_dim_with_indices(self.primitive, dim);
923
924        let tensor = Tensor::new(tensor);
925        let index = Tensor::new(index);
926
927        (tensor, index)
928    }
929
930    /// Finds the minimum pair wise values with another tensor.
931    ///
932    /// # Arguments
933    ///
934    /// * `other` - Other tensor to find minimum elements with
935    ///
936    /// # Returns
937    ///
938    /// A tensor with the same shape as the input tensors containing the minimum value found
939    /// between each element of the two source tensors.
940    ///
941    /// # Example
942    ///
943    /// ```rust
944    /// use burn_tensor::{Tensor, Shape};
945    ///
946    /// let device = Default::default();
947    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
948    /// let tensor2 = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
949    /// let tensor = tensor1.min_pair(tensor2);
950    /// println!("{tensor}");
951    /// // [[1.0, -2.0, 3.0], [1.0, 2.0, 3.0]]
952    /// ```
953    pub fn min_pair(self, other: Self) -> Self {
954        let mask = other.clone().lower(self.clone());
955        self.mask_where(mask, other)
956    }
957
958    /// Clamp element wise between the given min and max values.
959    ///
960    /// # Arguments
961    ///
962    /// * `min` - The minimum value.
963    /// * `max` - The maximum value.
964    ///
965    /// # Returns
966    ///
967    /// A new tensor with the values clamped between the given min and max values.
968    ///
969    /// # Example
970    ///
971    /// ```rust
972    /// use burn_tensor::{Int, Tensor};
973    ///
974    /// let device = Default::default();
975    /// let tensor = Tensor::<2, Int>::from_ints(
976    ///  [
977    ///   [1, 2, 3],
978    ///   [4, 5, 6],
979    ///   [7, 8, 9]
980    ///  ],
981    ///  &device);
982    ///  let tensor = tensor.clamp(2, 6);
983    ///  println!("{tensor}");
984    ///  // [[2, 2, 3], [4, 5, 6], [6, 6, 6]]
985    /// ```
986    pub fn clamp<E: ElementConversion>(self, min: E, max: E) -> Self {
987        let dtype = self.dtype();
988        Self::new(K::clamp(
989            self.primitive,
990            Scalar::new(min, &dtype),
991            Scalar::new(max, &dtype),
992        ))
993    }
994
995    /// Clamp element wise under a minimum value.
996    ///
997    /// # Arguments
998    ///
999    /// * `tensor` - The tensor to clamp.
1000    /// * `min` - The minimum value.
1001    ///
1002    /// # Returns
1003    ///
1004    /// A new tensor with the values clamped under the given min value.
1005    ///
1006    /// # Example
1007    ///
1008    /// ```rust
1009    /// use burn_tensor::{Int, Tensor};
1010    ///
1011    /// let device = Default::default();
1012    /// let tensor = Tensor::<2, Int>::from_ints(
1013    /// [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
1014    /// &device);
1015    /// let tensor = tensor.clamp_min(4);
1016    /// println!("{tensor}");
1017    /// // [[4, 4, 4], [4, 5, 6], [7, 8, 9]]
1018    /// ```
1019    pub fn clamp_min<E: ElementConversion>(self, min: E) -> Self {
1020        let min = Scalar::new(min, &self.dtype());
1021        Self::new(K::clamp_min(self.primitive, min))
1022    }
1023
1024    /// Clamp element wise over a maximum value.
1025    ///
1026    /// # Arguments
1027    ///
1028    /// * `tensor` - The tensor to clamp.
1029    /// * `max` - The maximum value.
1030    ///
1031    /// # Returns
1032    ///
1033    /// A new tensor with the values clamped over the given max value.
1034    ///
1035    /// # Example
1036    ///
1037    /// ```rust
1038    /// use burn_tensor::{Int, Tensor};
1039    ///
1040    /// let device = Default::default();
1041    /// let tensor = Tensor::<2, Int>::from_ints(
1042    /// [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
1043    /// &device);
1044    /// let tensor = tensor.clamp_max(5);
1045    /// println!("{tensor}");
1046    /// // [[1, 2, 3], [4, 5, 5], [5, 5, 5]]
1047    /// ```
1048    pub fn clamp_max<E: ElementConversion>(self, max: E) -> Self {
1049        let max = Scalar::new(max, &self.dtype());
1050        Self::new(K::clamp_max(self.primitive, max))
1051    }
1052
1053    /// Computes the cumulative minimum of elements along the given *dimension* or *axis*.
1054    ///
1055    /// # Arguments
1056    ///
1057    /// * `dim` - The dimension or axis along which to compute the cumulative minimum.
1058    ///   Negative dimensions are supported and count from the end.
1059    ///
1060    /// For floating-point tensors, once a NaN is encountered in a scan, the output at that
1061    /// position and every later position in the scan is NaN.
1062    ///
1063    /// # Example
1064    ///
1065    /// ```rust
1066    /// use burn_tensor::{Tensor, Shape};
1067    ///
1068    /// let device = Default::default();
1069    /// let tensor = Tensor::<2>::from_data([[3.0, 5.0, 2.0], [4.0, 1.0, 6.0]], &device);
1070    /// let result = tensor.clone().cummin(0);
1071    /// println!("{result}");
1072    /// // [[3.0, 5.0, 2.0], [3.0, 1.0, 2.0]]
1073    /// let result = tensor.cummin(1);
1074    /// println!("{result}");
1075    /// // [[3.0, 3.0, 2.0], [4.0, 1.0, 1.0]]
1076    /// ```
1077    pub fn cummin<I: AsIndex>(self, dim: I) -> Self {
1078        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cummin");
1079        Self::new(K::cummin(self.primitive, dim))
1080    }
1081
1082    /// Computes the cumulative maximum of elements along the given *dimension* or *axis*.
1083    ///
1084    /// # Arguments
1085    ///
1086    /// * `dim` - The dimension or axis along which to compute the cumulative maximum.
1087    ///   Negative dimensions are supported and count from the end.
1088    ///
1089    /// For floating-point tensors, once a NaN is encountered in a scan, the output at that
1090    /// position and every later position in the scan is NaN.
1091    ///
1092    /// # Example
1093    ///
1094    /// ```rust
1095    /// use burn_tensor::{Tensor, Shape};
1096    ///
1097    /// let device = Default::default();
1098    /// let tensor = Tensor::<2>::from_data([[3.0, 1.0, 2.0], [4.0, 5.0, 2.0]], &device);
1099    /// let result = tensor.clone().cummax(0);
1100    /// println!("{result}");
1101    /// // [[3.0, 1.0, 2.0], [4.0, 5.0, 2.0]]
1102    /// let result = tensor.cummax(1);
1103    /// println!("{result}");
1104    /// // [[3.0, 3.0, 3.0], [4.0, 5.0, 5.0]]
1105    /// ```
1106    pub fn cummax<I: AsIndex>(self, dim: I) -> Self {
1107        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cummax");
1108        Self::new(K::cummax(self.primitive, dim))
1109    }
1110    /// Find the maximum value along the given dimension.
1111    ///
1112    /// # Arguments
1113    ///
1114    /// * `dim` - The dimension or axis along which to aggregate the elements;
1115    ///   supports negative indexing.
1116    ///
1117    /// # Returns
1118    ///
1119    /// The returned tensor will have the same rank,
1120    /// but the aggregated dimension will have size 1.
1121    ///
1122    /// For floating-point tensors, a reduced slice produces NaN if it contains a NaN.
1123    ///
1124    /// # Example
1125    ///
1126    /// ```rust
1127    /// use burn_tensor::{Tensor, Shape};
1128    ///
1129    /// let device = Default::default();
1130    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
1131    /// let tensor = tensor.max_dim(0);
1132    /// println!("{tensor}");
1133    /// // [[5.0, 9.0, 6.0]]
1134    /// ```
1135    pub fn max_dim<I: AsIndex>(self, dim: I) -> Self {
1136        let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Dim");
1137        Tensor::new(K::max_dim(self.primitive, dim))
1138    }
1139
1140    /// Find the maximum value along the given dimensions.
1141    ///
1142    /// # Arguments
1143    ///
1144    /// * `dims` - The dimensions or axis along which to aggregate the elements;
1145    ///   supports negative indexing.
1146    ///
1147    /// # Returns
1148    ///
1149    /// The returned tensor will have the same rank,
1150    /// but the aggregated dimensions will have size 1.
1151    ///
1152    /// For floating-point tensors, a reduced region produces NaN if it contains a NaN.
1153    ///
1154    /// # Example
1155    ///
1156    /// ```rust
1157    /// use burn_tensor::{Tensor, Shape};
1158    ///
1159    /// let device = Default::default();
1160    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
1161    /// let tensor = tensor.max_dims(&[0, 1]);
1162    /// println!("{tensor}");
1163    /// // [[9.0]]
1164    /// ```
1165    pub fn max_dims<I: AsIndex>(self, dims: &[I]) -> Self {
1166        dims.iter().fold(self, |tensor, &dim| tensor.max_dim(dim))
1167    }
1168}