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