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