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