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