Skip to main content

burn_tensor/tensor/api/
orderable.rs

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