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