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::quantized_unsupported("One Hot", self.dtype()));
288        check!(TensorCheck::one_hot_tensor(self.clone(), num_classes));
289        self.one_hot_fill(num_classes, 1.0, 0.0, -1)
290    }
291
292    /// Create a one-hot encoded tensor with configurable `num_classes`, `on_value`, `off_value`, and `axis` including high-ranked tensors.
293    ///
294    /// # Arguments
295    ///
296    /// * `num_classes`: The number of classes for the one-hot encoding, which defines the size of the one-hot dimension.
297    /// * `on_value`: The value to assign for active positions (corresponding to indices).
298    /// * `off_value`: The value to assign for inactive positions.
299    /// * `axis`: The axis along which the one-hot dimension is added.
300    ///   Negative dimensions are supported and count from the end.
301    ///
302    /// # Returns
303    ///
304    /// A tensor with one additional dimension for the one-hot encoding, where active positions are filled with `on_value` and others with `off_value`.
305    ///
306    /// # Panics
307    ///
308    /// If the tensor is quantized, since lossy quantized values can't be trusted as class indices.
309    ///
310    /// # Example
311    /// ```rust
312    /// use burn_tensor::{Tensor, Float};
313    /// let device = Default::default();
314    /// let indices: Tensor<2, Float> = Tensor::from_floats([[0., 2.], [1., -1.]], &device);
315    /// // One-hot encoding
316    /// let tensor: Tensor<3, Float> = indices.one_hot_fill(3, 5.0.into(), 0.0.into(), -1);
317    /// println!("{tensor}");
318    /// // [[[5.0, 0.0, 0.0],
319    /// // [0.0, 0.0, 5.0]],
320    /// // [[0.0, 5.0, 0.0],
321    /// // [0.0, 0.0, 5.0]]]
322    /// ```
323    pub fn one_hot_fill<const D2: usize>(
324        self,
325        num_classes: usize,
326        on_value: f32,
327        off_value: f32,
328        axis: impl AsIndex,
329    ) -> Tensor<D2, K> {
330        check!(TensorCheck::one_hot_tensor_rank::<D, D2>());
331        check!(TensorCheck::quantized_unsupported("One Hot", self.dtype()));
332        let axis = unwrap_dim_index(axis.try_dim_index(D + 1), "One Hot");
333
334        // Initialize shape from the current tensor dimensions and prepare for modification
335        let mut shape = self.shape();
336        let device = self.device();
337        let dtype = self.dtype();
338
339        // Convert the input tensor to integer indices
340        let indices: Tensor<D, Int> = Tensor::from_data(self.to_data().convert::<i64>(), &device);
341        // Insert the new dimension for the one-hot representation
342        shape.insert(axis, num_classes);
343        // Adjust indices to valid range and handle invalid indices
344        let adjusted_indices = indices
345            .clone()
346            .mask_fill(self.clone().lower_scalar(0), num_classes as i64) // Handle negative indices
347            .add(indices.clone().mask_fill(self.clone().greater_scalar(0), 0)); // Handle positive indices
348
349        // Unsqueeze the indices tensor along the specified axis
350        let indices_unsqueezed: Tensor<D2, Int> = adjusted_indices.unsqueeze_dim(axis);
351
352        // Initialize the output tensor with the off_value
353        let output = Tensor::full(shape.clone(), off_value, (&device, dtype));
354
355        let on_values = Tensor::full(indices_unsqueezed.shape(), on_value, (&device, dtype));
356
357        // Scatter on_value at the appropriate indices to create the one-hot representation
358        output.scatter(
359            axis,
360            indices_unsqueezed,
361            on_values,
362            IndexingUpdateOp::Assign,
363        )
364    }
365
366    /// Applies element wise greater comparison and returns a boolean tensor.
367    ///
368    /// # Panics
369    ///
370    /// If the two tensors don't have the same shape.
371    ///
372    /// # Example
373    ///
374    /// ```rust
375    /// use burn_tensor::{Tensor, Shape};
376    ///
377    /// let device = Default::default();
378    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
379    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
380    /// let tensor = tensor1.greater(tensor2);
381    /// println!("{tensor}");
382    /// // [[false, false, false], [true, true, true]]
383    /// ```
384    pub fn greater(self, other: Self) -> Tensor<D, Bool> {
385        check!(TensorCheck::binary_ops_ew("Greater", &self, &other));
386        Tensor::new(K::greater(self.primitive, other.primitive))
387    }
388
389    /// Applies element wise greater-equal comparison and returns a boolean tensor.
390    ///
391    /// # Panics
392    ///
393    /// If the two tensors don't have the same shape.
394    ///
395    /// # Example
396    ///
397    /// ```rust
398    /// use burn_tensor::{Tensor, Shape};
399    ///
400    /// let device = Default::default();
401    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
402    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
403    /// let tensor = tensor1.greater_equal(tensor2);
404    /// println!("{tensor}");
405    /// // [[true, false, false], [true, true, true]]
406    /// ```
407    pub fn greater_equal(self, other: Self) -> Tensor<D, Bool> {
408        check!(TensorCheck::binary_ops_ew("Greater_equal", &self, &other));
409        Tensor::new(K::greater_equal(self.primitive, other.primitive))
410    }
411
412    /// Applies element wise lower comparison and returns a boolean tensor.
413    ///
414    /// # Panics
415    ///
416    /// If the two tensors don't have the same shape.
417    ///
418    /// # Example
419    ///
420    /// ```rust
421    /// use burn_tensor::{Tensor, Shape};
422    ///
423    /// let device = Default::default();
424    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
425    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
426    /// let tensor = tensor1.lower(tensor2);
427    /// println!("{tensor}");
428    /// // [[false, true, true], [false, false, false]]
429    /// ```
430    pub fn lower(self, other: Self) -> Tensor<D, Bool> {
431        check!(TensorCheck::binary_ops_ew("Lower", &self, &other));
432        Tensor::new(K::lower(self.primitive, other.primitive))
433    }
434
435    /// Applies element wise lower-equal comparison and returns a boolean tensor.
436    ///
437    /// # Panics
438    ///
439    /// If the two tensors don't have the same shape.
440    ///
441    /// # Example
442    ///
443    /// ```rust
444    /// use burn_tensor::{Tensor, Shape};
445    ///
446    /// let device = Default::default();
447    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
448    /// let tensor2 = Tensor::<2>::from_data([[1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
449    /// let tensor = tensor1.lower_equal(tensor2);
450    /// println!("{tensor}");
451    /// // [[true, true, true], [false, false, false]]
452    /// ```
453    pub fn lower_equal(self, other: Self) -> Tensor<D, Bool> {
454        check!(TensorCheck::binary_ops_ew("Lower_equal", &self, &other));
455        Tensor::new(K::lower_equal(self.primitive, other.primitive))
456    }
457
458    /// Applies greater than `other` comparison and returns a boolean tensor.
459    ///
460    /// # Arguments
461    ///
462    /// * `other` - The scalar to compare.
463    ///
464    /// # Example
465    ///
466    /// ```rust
467    /// use burn_tensor::{Tensor, Shape};
468    ///
469    /// let device = Default::default();
470    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
471    /// let tensor = tensor.greater_scalar(3.0);
472    /// println!("{tensor}");
473    /// // [[false, false, true], [true, true, true]]
474    /// ```
475    pub fn greater_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
476        let other = Scalar::new(other, &self.dtype());
477        Tensor::new(K::greater_scalar(self.primitive, other))
478    }
479
480    /// Applies greater-equal than `other` comparison and returns a boolean tensor.
481    ///
482    /// # Arguments
483    ///
484    /// * `other` - The scalar to compare.
485    ///
486    /// # Example
487    ///
488    /// ```rust
489    /// use burn_tensor::{Tensor, Shape};
490    ///
491    /// let device = Default::default();
492    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
493    /// let tensor = tensor.greater_equal_scalar(3.0);
494    /// println!("{tensor}");
495    /// // [[false, false, true], [true, true, true]]
496    /// ```
497    pub fn greater_equal_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
498        let other = Scalar::new(other, &self.dtype());
499        Tensor::new(K::greater_equal_scalar(self.primitive, other))
500    }
501
502    /// Applies lower than `other` comparison and returns a boolean tensor.
503    ///
504    /// # Arguments
505    ///
506    /// * `other` - The scalar to compare.
507    ///
508    /// # Example
509    ///
510    /// ```rust
511    /// use burn_tensor::{Tensor, Shape};
512    ///
513    /// let device = Default::default();
514    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
515    /// let tensor = tensor.lower_scalar(3.0);
516    /// println!("{tensor}");
517    /// // [[true, true, false], [false, false, false]]
518    /// ```
519    pub fn lower_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
520        let other = Scalar::new(other, &self.dtype());
521        Tensor::new(K::lower_scalar(self.primitive, other))
522    }
523
524    /// Applies lower-equal than `other` comparison and returns a boolean tensor.
525    ///
526    /// # Arguments
527    ///
528    /// * `other` - The scalar to compare.
529    ///
530    /// # Example
531    ///
532    /// ```rust
533    /// use burn_tensor::{Tensor, Shape};
534    ///
535    /// let device = Default::default();
536    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
537    /// let tensor = tensor.lower_equal_scalar(3.0);
538    /// println!("{tensor}");
539    /// // [[true, true, true], [false, false, false]]
540    /// ```
541    pub fn lower_equal_scalar<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
542        let other = Scalar::new(other, &self.dtype());
543        Tensor::new(K::lower_equal_scalar(self.primitive, other))
544    }
545
546    /// Alias for [greater_scalar](Self::greater_scalar).
547    pub fn greater_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
548        self.greater_scalar(other)
549    }
550
551    /// Alias for [greater_equal_scalar](Self::greater_equal_scalar).
552    pub fn greater_equal_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
553        self.greater_equal_scalar(other)
554    }
555
556    /// Alias for [lower_scalar](Self::lower_scalar).
557    pub fn lower_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
558        self.lower_scalar(other)
559    }
560
561    /// Alias for [lower_equal_scalar](Self::lower_equal_scalar).
562    pub fn lower_equal_elem<E: ElementConversion>(self, other: E) -> Tensor<D, Bool> {
563        self.lower_equal_scalar(other)
564    }
565
566    /// Applies the argmax function along the given dimension and returns an integer tensor.
567    ///
568    /// # Arguments
569    ///
570    /// * `dim` - The dimension along which to find the maximum value.
571    ///   Negative dimensions are supported and count from the end.
572    ///
573    /// # NaN behavior
574    ///
575    /// For floating-point tensors, NaNs take precedence over non-NaN values. If a reduced slice
576    /// contains multiple NaNs, the lowest coordinate along `dim` is returned. Non-NaN ties also
577    /// return the lowest coordinate.
578    ///
579    /// # Example
580    ///
581    /// ```rust
582    /// use burn_tensor::{Tensor, Shape};
583    ///
584    /// let device = Default::default();
585    /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 3]), &device);
586    /// let tensor = tensor.argmax(1);
587    /// println!("{:?}", tensor.shape());
588    /// // Shape { dims: [2, 1, 3] }
589    ///
590    /// let tensor = Tensor::<1>::from_data([3.0, f32::NAN, f32::NAN], &device);
591    /// let index: i32 = tensor.argmax(0).into_scalar();
592    /// assert_eq!(index, 1);
593    /// ```
594    pub fn argmax(self, dim: impl AsIndex) -> Tensor<D, Int> {
595        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argmax");
596        Tensor::new(K::argmax(self.primitive, dim))
597    }
598
599    /// Applies the argtopk function along the given dimension and returns an integer tensor.
600    ///
601    /// # Arguments
602    ///
603    /// * `k` - The number of indices to return.
604    /// * `dim` - The dimension along which to find the largest values.
605    ///   Negative dimensions are supported and count from the end.
606    ///
607    /// # Example
608    ///
609    /// ```rust
610    /// use burn_tensor::{Tensor, Shape};
611    ///
612    /// let device = Default::default();
613    /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 3]), &device);
614    /// let tensor = tensor.argtopk(1, 2);
615    /// println!("{:?}", tensor.shape());
616    /// ```
617    pub fn argtopk(self, k: usize, dim: impl AsIndex) -> Tensor<D, Int> {
618        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argtopk");
619        assert!(self.shape()[dim] > k);
620        Tensor::new(K::argtopk(self.primitive, dim, k))
621    }
622
623    /// Find the maximum value.
624    ///
625    /// For floating-point tensors, the result is NaN if any element is NaN.
626    ///
627    /// # Example
628    ///
629    /// ```rust
630    /// use burn_tensor::{Tensor, Shape};
631    ///
632    /// let device = Default::default();
633    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
634    /// let tensor = tensor.max();
635    /// println!("{tensor}");
636    /// // [9.0]
637    ///
638    /// let tensor = Tensor::<1>::from_data([1.0, f32::NAN, 3.0], &device);
639    /// let value: f32 = tensor.max().into_scalar();
640    /// assert!(value.is_nan());
641    /// ```
642    pub fn max(self) -> Tensor<1, K> {
643        Tensor::new(K::max(self.primitive))
644    }
645
646    /// Find the maximum value along the given dimension.
647    ///
648    /// Also returns the indices.
649    ///
650    /// For floating-point tensors, a NaN in a reduced slice produces a NaN value. The returned
651    /// index is the lowest coordinate containing NaN. Non-NaN ties also return the lowest
652    /// coordinate.
653    ///
654    /// # Example
655    ///
656    /// ```rust
657    /// use burn_tensor::{Tensor, Shape};
658    ///
659    /// let device = Default::default();
660    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
661    /// let (tensor, index) = tensor.max_dim_with_indices(0);
662    /// // [[5.0, 9.0, 6.0]]
663    /// println!("{tensor}");
664    /// // [[1, 1, 1]]
665    /// println!("{index}");
666    /// ```
667    pub fn max_dim_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
668        let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Dim With Indices");
669
670        let (tensor, index) = K::max_dim_with_indices(self.primitive, dim);
671
672        let tensor = Tensor::new(tensor);
673        let index = Tensor::new(index);
674
675        (tensor, index)
676    }
677
678    /// Find the maximum absolute value.
679    ///
680    /// For floating-point tensors, the result is NaN if any element is NaN.
681    ///
682    /// # Example
683    ///
684    /// ```rust
685    /// use burn_tensor::{Tensor, Shape};
686    ///
687    /// let device = Default::default();
688    /// let tensor = Tensor::<2>::from_data([[1.0, -7.0, 3.0], [5.0, -1.0, 6.0]], &device);
689    /// let tensor = tensor.max_abs();
690    /// println!("{tensor}");
691    /// // [7.0]
692    /// ```
693    pub fn max_abs(self) -> Tensor<1, K> {
694        Tensor::new(K::max_abs(self.primitive))
695    }
696
697    /// Finds the maximum pair wise values with another tensor.
698    ///
699    /// # Arguments
700    ///
701    /// * `other` - Other tensor to find maximum elements with
702    ///
703    /// # Returns
704    ///
705    /// A tensor with the same shape as the input tensors containing the maximum value found
706    /// in the input tensors.
707    ///
708    /// # Example
709    ///
710    /// ```rust
711    /// use burn_tensor::{Tensor, Shape};
712    ///
713    /// let device = Default::default();
714    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
715    /// let tensor2 = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
716    /// let tensor = tensor1.max_pair(tensor2);
717    /// println!("{tensor}");
718    /// // [[2.0, 3.0, 4.0], [5.0, 9.0, 6.0]]
719    /// ```
720    pub fn max_pair(self, other: Self) -> Self {
721        let mask = self.clone().lower(other.clone());
722        self.mask_where(mask, other)
723    }
724
725    /// Find the maximum absolute value along the given dimension.
726    ///
727    /// # Arguments
728    ///
729    /// * `dim` - The dimension or axis along which to aggregate the elements,
730    ///   supports negative indexing.
731    ///
732    /// # Returns
733    ///
734    /// The returned tensor will have the same rank,
735    /// but the aggregated dimension will have size 1.
736    ///
737    /// For floating-point tensors, a reduced slice produces NaN if it contains a NaN.
738    ///
739    /// # Example
740    ///
741    /// ```rust
742    /// use burn_tensor::{Tensor, Shape};
743    ///
744    /// let device = Default::default();
745    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
746    /// let tensor = tensor.max_dim(0);
747    /// println!("{tensor}");
748    /// // [[5.0, 9.0, 6.0]]
749    /// ```
750    pub fn max_abs_dim<I: AsIndex>(self, dim: I) -> Self {
751        let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Abs Dim");
752
753        Tensor::new(K::max_abs_dim(self.primitive, dim))
754    }
755
756    /// Find the maximum absolute value along the given dimensions.
757    ///
758    /// # Arguments
759    ///
760    /// * `dims` - The dimensions or axes along which to aggregate the elements,
761    ///   supports negative indexing.
762    ///
763    /// # Returns
764    ///
765    /// The returned tensor will have the same rank,
766    /// but the aggregated dimensions will have size 1.
767    ///
768    /// For floating-point tensors, a reduced region produces NaN if it contains a NaN.
769    ///
770    /// # Example
771    ///
772    /// ```rust
773    /// use burn_tensor::{Tensor, Shape};
774    ///
775    /// let device = Default::default();
776    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
777    /// let tensor = tensor.max_abs_dims(&[0, 1]);
778    /// println!("{tensor}");
779    /// // [[9.0]]
780    /// ```
781    ///
782    /// # Empty dimensions
783    ///
784    /// Unlike ordinary reductions such as [`Tensor::max_dims`], this is a composite
785    /// operation equivalent to `self.abs().max_dims(dims)`. If `dims` is empty, no
786    /// reduction is performed, but the elementwise absolute-value operation is
787    /// still applied.
788    pub fn max_abs_dims<I: AsIndex>(self, dims: &[I]) -> Self {
789        if dims.is_empty() {
790            return self.abs();
791        }
792        dims.iter()
793            .fold(self, |tensor, &dim| tensor.max_abs_dim(dim))
794    }
795
796    /// Applies the argmin function along the given dimension and returns an integer tensor.
797    ///
798    /// # Arguments
799    ///
800    /// * `dim` - The dimension along which to find the minimum value.
801    ///   Negative dimensions are supported and count from the end.
802    ///
803    /// # NaN behavior
804    ///
805    /// For floating-point tensors, NaNs take precedence over non-NaN values. If a reduced slice
806    /// contains multiple NaNs, the lowest coordinate along `dim` is returned. Non-NaN ties also
807    /// return the lowest coordinate.
808    ///
809    /// # Example
810    ///
811    /// ```rust
812    /// use burn_tensor::{Tensor, Shape};
813    ///
814    /// let device = Default::default();
815    /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 3]), &device);
816    /// let tensor = tensor.argmin(1);
817    /// println!("{:?}", tensor.shape());
818    /// // Shape { dims: [2, 1, 3] }
819    /// ```
820    pub fn argmin(self, dim: impl AsIndex) -> Tensor<D, Int> {
821        let dim = unwrap_dim_index(dim.try_dim_index(D), "Argmin");
822        Tensor::new(K::argmin(self.primitive, dim))
823    }
824
825    /// Find the minimum value.
826    ///
827    /// For floating-point tensors, the result is NaN if any element is NaN.
828    ///
829    /// # Example
830    ///
831    /// ```rust
832    /// use burn_tensor::{Tensor, Shape};
833    ///
834    /// let device = Default::default();
835    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
836    /// let tensor = tensor.min();
837    /// println!("{tensor}");
838    /// // [-2.0]
839    /// ```
840    pub fn min(self) -> Tensor<1, K> {
841        Tensor::new(K::min(self.primitive))
842    }
843
844    /// Find the minimum value along the given dimension.
845    ///
846    /// # Arguments
847    ///
848    /// * `dim` - The dimension or axis along which to aggregate the elements;
849    ///   supports negative indexing.
850    ///
851    /// # Returns
852    ///
853    /// The returned tensor will have the same rank,
854    /// but the aggregated dimension will have size 1.
855    ///
856    /// For floating-point tensors, a reduced slice produces NaN if it contains a NaN.
857    ///
858    /// # Example
859    ///
860    /// ```rust
861    /// use burn_tensor::{Tensor, Shape};
862    ///
863    /// let device = Default::default();
864    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
865    /// let tensor = tensor.min_dim(0);
866    /// println!("{tensor}");
867    /// // [[1.0, -2.0, 3.0]]
868    /// ```
869    pub fn min_dim<I: AsIndex>(self, dim: I) -> Self {
870        let dim = unwrap_dim_index(dim.try_dim_index(D), "Min Dim");
871        Tensor::new(K::min_dim(self.primitive, dim))
872    }
873
874    /// Find the minimum value along the given dimensions.
875    ///
876    /// # Arguments
877    ///
878    /// * `dims` - The dimensions or axes along which to aggregate the elements;
879    ///   supports negative indexing.
880    ///
881    /// # Returns
882    ///
883    /// The returned tensor will have the same rank,
884    /// but the aggregated dimensions will have size 1.
885    ///
886    /// For floating-point tensors, a reduced region produces NaN if it contains a NaN.
887    ///
888    /// # Example
889    ///
890    /// ```rust
891    /// use burn_tensor::{Tensor, Shape};
892    ///
893    /// let device = Default::default();
894    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
895    /// let tensor = tensor.min_dims(&[0, 1]);
896    /// println!("{tensor}");
897    /// // [[-2.0]]
898    /// ```
899    pub fn min_dims<I: AsIndex>(self, dims: &[I]) -> Self {
900        dims.iter().fold(self, |tensor, &dim| tensor.min_dim(dim))
901    }
902
903    /// Find the minimum value along the given dimension.
904    ///
905    /// Also returns the indices.
906    ///
907    /// For floating-point tensors, a NaN in a reduced slice produces a NaN value. The returned
908    /// index is the lowest coordinate containing NaN. Non-NaN ties also return the lowest
909    /// coordinate.
910    ///
911    /// # Example
912    ///
913    /// ```rust
914    /// use burn_tensor::{Tensor, Shape};
915    ///
916    /// let device = Default::default();
917    /// let tensor = Tensor::<2>::from_data([[7.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
918    /// let (tensor, index) = tensor.min_dim_with_indices(0);
919    /// println!("{tensor}");
920    /// // [[5.0, -2.0, 3.0]]
921    /// println!("{}", index);
922    /// // [[1, 0, 0]]
923    /// ```
924    pub fn min_dim_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
925        let dim = unwrap_dim_index(dim.try_dim_index(D), "Min Dim With Indices");
926
927        let (tensor, index) = K::min_dim_with_indices(self.primitive, dim);
928
929        let tensor = Tensor::new(tensor);
930        let index = Tensor::new(index);
931
932        (tensor, index)
933    }
934
935    /// Finds the minimum pair wise values with another tensor.
936    ///
937    /// # Arguments
938    ///
939    /// * `other` - Other tensor to find minimum elements with
940    ///
941    /// # Returns
942    ///
943    /// A tensor with the same shape as the input tensors containing the minimum value found
944    /// between each element of the two source tensors.
945    ///
946    /// # Example
947    ///
948    /// ```rust
949    /// use burn_tensor::{Tensor, Shape};
950    ///
951    /// let device = Default::default();
952    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
953    /// let tensor2 = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
954    /// let tensor = tensor1.min_pair(tensor2);
955    /// println!("{tensor}");
956    /// // [[1.0, -2.0, 3.0], [1.0, 2.0, 3.0]]
957    /// ```
958    pub fn min_pair(self, other: Self) -> Self {
959        let mask = other.clone().lower(self.clone());
960        self.mask_where(mask, other)
961    }
962
963    /// Clamp element wise between the given min and max values.
964    ///
965    /// For floating-point tensors, if the result is zero and the input and bounds
966    /// include zeros of opposite signs, either sign of zero may be returned.
967    ///
968    /// # Arguments
969    ///
970    /// * `min` - The minimum value.
971    /// * `max` - The maximum value.
972    ///
973    /// # Returns
974    ///
975    /// A new tensor with the values clamped between the given min and max values.
976    ///
977    /// # Example
978    ///
979    /// ```rust
980    /// use burn_tensor::{Int, Tensor};
981    ///
982    /// let device = Default::default();
983    /// let tensor = Tensor::<2, Int>::from_ints(
984    ///  [
985    ///   [1, 2, 3],
986    ///   [4, 5, 6],
987    ///   [7, 8, 9]
988    ///  ],
989    ///  &device);
990    ///  let tensor = tensor.clamp(2, 6);
991    ///  println!("{tensor}");
992    ///  // [[2, 2, 3], [4, 5, 6], [6, 6, 6]]
993    /// ```
994    pub fn clamp<E: ElementConversion>(self, min: E, max: E) -> Self {
995        let dtype = self.dtype();
996        Self::new(K::clamp(
997            self.primitive,
998            Scalar::new(min, &dtype),
999            Scalar::new(max, &dtype),
1000        ))
1001    }
1002
1003    /// Clamp element wise under a minimum value.
1004    ///
1005    /// # Arguments
1006    ///
1007    /// * `tensor` - The tensor to clamp.
1008    /// * `min` - The minimum value.
1009    ///
1010    /// # Returns
1011    ///
1012    /// A new tensor with the values clamped under the given min value.
1013    ///
1014    /// # Example
1015    ///
1016    /// ```rust
1017    /// use burn_tensor::{Int, Tensor};
1018    ///
1019    /// let device = Default::default();
1020    /// let tensor = Tensor::<2, Int>::from_ints(
1021    /// [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
1022    /// &device);
1023    /// let tensor = tensor.clamp_min(4);
1024    /// println!("{tensor}");
1025    /// // [[4, 4, 4], [4, 5, 6], [7, 8, 9]]
1026    /// ```
1027    pub fn clamp_min<E: ElementConversion>(self, min: E) -> Self {
1028        let min = Scalar::new(min, &self.dtype());
1029        Self::new(K::clamp_min(self.primitive, min))
1030    }
1031
1032    /// Clamp element wise over a maximum value.
1033    ///
1034    /// # Arguments
1035    ///
1036    /// * `tensor` - The tensor to clamp.
1037    /// * `max` - The maximum value.
1038    ///
1039    /// # Returns
1040    ///
1041    /// A new tensor with the values clamped over the given max value.
1042    ///
1043    /// # Example
1044    ///
1045    /// ```rust
1046    /// use burn_tensor::{Int, Tensor};
1047    ///
1048    /// let device = Default::default();
1049    /// let tensor = Tensor::<2, Int>::from_ints(
1050    /// [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
1051    /// &device);
1052    /// let tensor = tensor.clamp_max(5);
1053    /// println!("{tensor}");
1054    /// // [[1, 2, 3], [4, 5, 5], [5, 5, 5]]
1055    /// ```
1056    pub fn clamp_max<E: ElementConversion>(self, max: E) -> Self {
1057        let max = Scalar::new(max, &self.dtype());
1058        Self::new(K::clamp_max(self.primitive, max))
1059    }
1060
1061    /// Computes the cumulative minimum of elements along the given *dimension* or *axis*.
1062    ///
1063    /// # Arguments
1064    ///
1065    /// * `dim` - The dimension or axis along which to compute the cumulative minimum.
1066    ///   Negative dimensions are supported and count from the end.
1067    ///
1068    /// For floating-point tensors, once a NaN is encountered in a scan, the output at that
1069    /// position and every later position in the scan is NaN.
1070    ///
1071    /// # Example
1072    ///
1073    /// ```rust
1074    /// use burn_tensor::{Tensor, Shape};
1075    ///
1076    /// let device = Default::default();
1077    /// let tensor = Tensor::<2>::from_data([[3.0, 5.0, 2.0], [4.0, 1.0, 6.0]], &device);
1078    /// let result = tensor.clone().cummin(0);
1079    /// println!("{result}");
1080    /// // [[3.0, 5.0, 2.0], [3.0, 1.0, 2.0]]
1081    /// let result = tensor.cummin(1);
1082    /// println!("{result}");
1083    /// // [[3.0, 3.0, 2.0], [4.0, 1.0, 1.0]]
1084    /// ```
1085    pub fn cummin<I: AsIndex>(self, dim: I) -> Self {
1086        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cummin");
1087        Self::new(K::cummin(self.primitive, dim))
1088    }
1089
1090    /// Computes the cumulative maximum of elements along the given *dimension* or *axis*.
1091    ///
1092    /// # Arguments
1093    ///
1094    /// * `dim` - The dimension or axis along which to compute the cumulative maximum.
1095    ///   Negative dimensions are supported and count from the end.
1096    ///
1097    /// For floating-point tensors, once a NaN is encountered in a scan, the output at that
1098    /// position and every later position in the scan is NaN.
1099    ///
1100    /// # Example
1101    ///
1102    /// ```rust
1103    /// use burn_tensor::{Tensor, Shape};
1104    ///
1105    /// let device = Default::default();
1106    /// let tensor = Tensor::<2>::from_data([[3.0, 1.0, 2.0], [4.0, 5.0, 2.0]], &device);
1107    /// let result = tensor.clone().cummax(0);
1108    /// println!("{result}");
1109    /// // [[3.0, 1.0, 2.0], [4.0, 5.0, 2.0]]
1110    /// let result = tensor.cummax(1);
1111    /// println!("{result}");
1112    /// // [[3.0, 3.0, 3.0], [4.0, 5.0, 5.0]]
1113    /// ```
1114    pub fn cummax<I: AsIndex>(self, dim: I) -> Self {
1115        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cummax");
1116        Self::new(K::cummax(self.primitive, dim))
1117    }
1118    /// Find the maximum value along the given dimension.
1119    ///
1120    /// # Arguments
1121    ///
1122    /// * `dim` - The dimension or axis along which to aggregate the elements;
1123    ///   supports negative indexing.
1124    ///
1125    /// # Returns
1126    ///
1127    /// The returned tensor will have the same rank,
1128    /// but the aggregated dimension will have size 1.
1129    ///
1130    /// For floating-point tensors, a reduced slice produces NaN if it contains a NaN.
1131    ///
1132    /// # Example
1133    ///
1134    /// ```rust
1135    /// use burn_tensor::{Tensor, Shape};
1136    ///
1137    /// let device = Default::default();
1138    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
1139    /// let tensor = tensor.max_dim(0);
1140    /// println!("{tensor}");
1141    /// // [[5.0, 9.0, 6.0]]
1142    /// ```
1143    pub fn max_dim<I: AsIndex>(self, dim: I) -> Self {
1144        let dim = unwrap_dim_index(dim.try_dim_index(D), "Max Dim");
1145        Tensor::new(K::max_dim(self.primitive, dim))
1146    }
1147
1148    /// Find the maximum value along the given dimensions.
1149    ///
1150    /// # Arguments
1151    ///
1152    /// * `dims` - The dimensions or axis along which to aggregate the elements;
1153    ///   supports negative indexing.
1154    ///
1155    /// # Returns
1156    ///
1157    /// The returned tensor will have the same rank,
1158    /// but the aggregated dimensions will have size 1.
1159    ///
1160    /// For floating-point tensors, a reduced region produces NaN if it contains a NaN.
1161    ///
1162    /// # Example
1163    ///
1164    /// ```rust
1165    /// use burn_tensor::{Tensor, Shape};
1166    ///
1167    /// let device = Default::default();
1168    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
1169    /// let tensor = tensor.max_dims(&[0, 1]);
1170    /// println!("{tensor}");
1171    /// // [[9.0]]
1172    /// ```
1173    pub fn max_dims<I: AsIndex>(self, dims: &[I]) -> Self {
1174        dims.iter().fold(self, |tensor, &dim| tensor.max_dim(dim))
1175    }
1176}