burn_tensor/tensor/api/base.rs
1#![allow(clippy::single_range_in_vec_init)]
2use crate::check::{unwrap_dim_index, unwrap_shape_reshape};
3use crate::kind::Basic;
4use crate::ops::BridgeTensor;
5
6use burn_backend::Scalar;
7
8use alloc::vec::Vec;
9
10use alloc::format;
11use alloc::string::String;
12use alloc::vec;
13
14use burn_std::{ExecutionError, TensorReadError};
15use burn_std::{SliceOps, sync::RwLock};
16use core::iter::ExactSizeIterator;
17use core::iter::repeat;
18use core::marker::PhantomData;
19use core::{fmt::Debug, ops::Range};
20use serde::{Deserialize, Deserializer};
21
22use crate::ops::{BasicOps, Kind};
23use crate::{AsIndex, Device, Slice, SliceArg, wrap_index};
24use crate::{Bool, ElementConversion, Float, Int, Shape, TensorData, check};
25use crate::{DType, Element};
26use crate::{IndexingUpdateOp, TensorCreationOptions};
27use crate::{cast::ToElement, check::TensorCheck};
28use core::future::Future;
29use serde::{Serialize, Serializer};
30
31/// A tensor with a given backend, shape and data type.
32///
33/// # Indexing
34///
35/// Indexing a tensor can be done using [`slice`](Tensor::slice) for all tensor types
36/// or [`select`](Tensor::select) for numeric types.
37///
38/// ## Example
39///
40/// ```rust
41/// use burn_tensor::Tensor;
42/// use burn_tensor::Int;
43///
44/// let device = Default::default();
45///
46/// let tensor = Tensor::<2>::from_data(
47/// [
48/// [3.0, 4.9, 2.0],
49/// [2.0, 1.9, 3.0],
50/// [6.0, 1.5, 7.0],
51/// [3.0, 4.9, 9.0],
52/// ],
53/// &device,
54/// );
55///
56/// // Slice the tensor to get the second and third rows:
57/// // [[2.0, 1.9, 3.0], [6.0, 1.5, 7.0]]
58/// // The resulting tensor will have dimensions [2, 3].
59/// let slice = tensor.clone().slice([1..3]);
60/// println!("{slice}");
61///
62/// // Slice the tensor to get the first two rows and the first 2 columns:
63/// // [[3.0, 4.9], [2.0, 1.9]]
64/// // The resulting tensor will have dimensions [2, 2].
65/// let slice = tensor.clone().slice([0..2, 0..2]);
66/// println!("{slice}");
67///
68/// // Index the tensor along the dimension 1 to get the elements 0 and 2:
69/// // [[3.0, 2.0], [2.0, 3.0], [6.0, 7.0], [3.0, 9.0]]
70/// // The resulting tensor will have dimensions [4, 2]
71/// let indices = Tensor::<1, Int>::from_data([0, 2], &device);
72/// let indexed = tensor.select(1, indices);
73/// println!("{indexed}");
74/// ```
75#[derive(new, Clone, Debug)]
76pub struct Tensor<const D: usize, K = Float>
77where
78 K: Basic,
79{
80 pub(crate) primitive: BridgeTensor,
81 _kind: PhantomData<K>,
82}
83
84impl<const D: usize, K, T> From<T> for Tensor<D, K>
85where
86 K: Basic,
87 T: Into<TensorData>,
88{
89 fn from(value: T) -> Self {
90 Tensor::from_data(value.into(), &Default::default())
91 }
92}
93
94impl<const D: usize, K> Tensor<D, K>
95where
96 K: Basic,
97{
98 /// Takes ownership of the tensor out of `self`, leaving an empty
99 /// zero-shape placeholder tensor in its place.
100 ///
101 /// This is analogous to [`Option::take`] / [`core::mem::take`]: it lets you
102 /// obtain an owned `Tensor` from behind a `&mut Tensor` so you can call
103 /// owned operations on it.
104 #[allow(unused_must_use)]
105 pub fn extract(&mut self) -> Self {
106 let mut z = Tensor::empty([0; D], &self.device());
107 core::mem::swap(self, &mut z);
108 z
109 }
110
111 /// Executes an operation on the tensor and modifies its value.
112 ///
113 /// # Notes
114 ///
115 /// This won't necessarily reuse the same tensor data/buffer, but it should if there is
116 /// no other reference pointing to the same tensor.
117 ///
118 /// Wrapping operations with inplace is not an optimization, it's mainly there if you
119 /// want to mutate a tensor by using owned operations. A plausible usage would be to
120 /// update the weights of a mutable model reference.
121 pub fn inplace<F: FnOnce(Self) -> Self>(&mut self, func: F) {
122 let mut z = func(self.extract());
123 core::mem::swap(self, &mut z);
124 }
125
126 /// Returns the number of dimensions of the tensor.
127 pub fn rank(&self) -> usize {
128 self.primitive.rank()
129 }
130
131 /// Returns the tensor primitive data type.
132 ///
133 /// # Note
134 /// Some element types are encoded in different primitive types depending on the backend
135 /// (e.g., bool could be encoded as `u8` or `u32`).
136 pub fn dtype(&self) -> DType {
137 self.primitive.dtype()
138 }
139
140 /// Whether this tensor's buffer can be mutated in place — i.e. this handle
141 /// uniquely owns the allocation, so an in-place op writes it directly
142 /// instead of copying first (see `TensorMetadata::can_mut`).
143 ///
144 /// Backends that track buffer ownership (cubecl, fusion, tch) answer
145 /// precisely from the handle reference count; others conservatively return
146 /// `false` — they may alias the buffer, so an in-place write can't be
147 /// assumed safe. Useful to assert a hot-path op (e.g. a KV-cache
148 /// `slice_assign`) stays in place rather than silently copying.
149 pub fn can_mut(&self) -> bool {
150 self.primitive.can_mut()
151 }
152
153 /// Create an empty tensor of the given shape.
154 ///
155 /// # Arguments
156 ///
157 /// - `shape`: The shape of the tensor.
158 /// - `device`: The device where the tensor will be created.
159 ///
160 /// # Example
161 /// ```rust
162 /// use burn_tensor::Tensor;
163 ///
164 /// let device = Default::default();
165 /// // Create an empty tensor with dimensions [2, 3, 4].
166 /// let tensor = Tensor::<3>::empty([2, 3, 4], &device);
167 /// ```
168 pub fn empty<S: Into<Shape>>(shape: S, options: impl Into<TensorCreationOptions>) -> Self {
169 let opt = options.into();
170 let shape = shape.into();
171 let dtype = opt.resolve_dtype::<K>();
172 check!(TensorCheck::creation_ops::<D>("Empty", &shape));
173 Self::new(K::empty(shape, &opt.device, dtype))
174 }
175
176 /// Create an empty tensor with the same shape, dtype, and device as the current tensor.
177 ///
178 /// # Panics
179 ///
180 /// If the tensor is quantized. This method preserves the input dtype,
181 /// but quantized tensor creation is not supported.
182 ///
183 /// # Example
184 /// ```rust
185 /// use burn_tensor::Tensor;
186 ///
187 /// let device = Default::default();
188 /// // Create a zeroed tensor with dimensions [2, 3, 4].
189 /// let tensor = Tensor::<3>::zeros([2, 3, 4], &device);
190 /// // Create an empty tensor with dimensions [2, 3, 4].
191 /// let tensor = tensor.empty_like();
192 /// ```
193 pub fn empty_like(&self) -> Self {
194 let dtype = self.dtype();
195 check!(TensorCheck::quantized_unsupported("Empty Like", dtype));
196 Self::new(K::empty(self.shape(), &self.device(), dtype))
197 }
198
199 /// The dtype to create a new tensor with so that it matches this one, used by ops that
200 /// build their output from scratch (e.g. short-circuiting to an empty result). Quantized
201 /// dtypes fall back to the device default float dtype, since tensors can't be created
202 /// directly in a quantized dtype.
203 pub(crate) fn creation_dtype(&self) -> DType {
204 match self.dtype() {
205 DType::QFloat(_) => TensorCreationOptions::new(self.device()).resolve_dtype::<K>(),
206 dtype => dtype,
207 }
208 }
209
210 /// Create a tensor of the given shape where each element is zero.
211 ///
212 /// # Example
213 ///
214 /// ```rust
215 /// use burn_tensor::{Tensor, Shape};
216 ///
217 /// let device = Default::default();
218 /// let tensor = Tensor::<2>::zeros(Shape::new([2, 3]), &device);
219 /// println!("{tensor}");
220 /// // [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
221 /// ```
222 pub fn zeros<S: Into<Shape>>(shape: S, options: impl Into<TensorCreationOptions>) -> Self {
223 let opt = options.into();
224 let shape = shape.into();
225 let dtype = opt.resolve_dtype::<K>();
226 check!(TensorCheck::creation_ops::<D>("Zeros", &shape));
227 Self::new(K::zeros(shape, &opt.device, dtype))
228 }
229
230 /// Returns a new tensor with the same shape, dtype, and device as the current tensor filled with zeros.
231 ///
232 /// # Panics
233 ///
234 /// If the tensor is quantized. This method preserves the input dtype,
235 /// but quantized tensor creation is not supported.
236 ///
237 /// # Example
238 ///
239 /// ```rust
240 /// use burn_tensor::{Tensor, Shape};
241 ///
242 /// let device = Default::default();
243 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
244 /// let tensor = tensor.zeros_like();
245 /// println!("{tensor}");
246 /// // [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
247 /// ```
248 pub fn zeros_like(&self) -> Self {
249 let dtype = self.dtype();
250 check!(TensorCheck::quantized_unsupported("Zeros Like", dtype));
251 Self::new(K::zeros(self.shape(), &self.device(), dtype))
252 }
253
254 /// Create a tensor of the given shape where each element is one.
255 ///
256 /// # Example
257 ///
258 /// ```rust
259 /// use burn_tensor::{Tensor, Shape};
260 ///
261 /// let device = Default::default();
262 /// let tensor = Tensor::<2>::ones(Shape::new([2, 3]), &device);
263 /// println!("{tensor}");
264 /// // [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]
265 /// ```
266 pub fn ones<S: Into<Shape>>(shape: S, options: impl Into<TensorCreationOptions>) -> Self {
267 let opt = options.into();
268 let shape = shape.into();
269 let dtype = opt.resolve_dtype::<K>();
270 check!(TensorCheck::creation_ops::<D>("Ones", &shape));
271 Self::new(K::ones(shape, &opt.device, dtype))
272 }
273
274 /// Returns a new tensor with the same shape, dtype, and device as the current tensor filled with ones.
275 ///
276 /// # Panics
277 ///
278 /// If the tensor is quantized. This method preserves the input dtype,
279 /// but quantized tensor creation is not supported.
280 ///
281 /// # Example
282 ///
283 /// ```rust
284 /// use burn_tensor::{Tensor, Shape};
285 ///
286 /// let device = Default::default();
287 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
288 /// let tensor = tensor.ones_like();
289 /// println!("{tensor}");
290 /// // [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]
291 /// ```
292 pub fn ones_like(&self) -> Self {
293 let dtype = self.dtype();
294 check!(TensorCheck::quantized_unsupported("Ones Like", dtype));
295 Self::new(K::ones(self.shape(), &self.device(), dtype))
296 }
297
298 /// Create a tensor of the given shape where each element is equal to the provided value.
299 ///
300 /// # Example
301 ///
302 /// ```rust
303 /// use burn_tensor::{Tensor, Shape};
304 ///
305 /// let device = Default::default();
306 /// let tensor = Tensor::<2>::full(Shape::new([2, 3]), 5.0, &device);
307 /// println!("{tensor}");
308 /// // [[5.0, 5.0, 5.0], [5.0, 5.0, 5.0]]
309 /// ```
310 pub fn full<S: Into<Shape>, E: ElementConversion>(
311 shape: S,
312 fill_value: E,
313 options: impl Into<TensorCreationOptions>,
314 ) -> Self {
315 let opt = options.into();
316 let shape = shape.into();
317 let dtype = opt.resolve_dtype::<K>();
318 check!(TensorCheck::creation_ops::<D>("Full", &shape));
319 Self::new(K::full(
320 shape,
321 Scalar::new(fill_value, &dtype),
322 &opt.device,
323 dtype,
324 ))
325 }
326
327 /// Returns a new tensor with the same shape, dtype, and device as the current tensor,
328 /// filled with the provided value.
329 ///
330 /// # Panics
331 ///
332 /// If the tensor is quantized. This method preserves the input dtype,
333 /// but quantized tensor creation is not supported.
334 ///
335 /// # Example
336 ///
337 /// ```rust
338 /// use burn_tensor::{Tensor, Shape};
339 ///
340 /// let device = Default::default();
341 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
342 /// let tensor = tensor.full_like(5.0);
343 /// println!("{tensor}");
344 /// // [[5.0, 5.0, 5.0], [5.0, 5.0, 5.0]]
345 /// ```
346 pub fn full_like<E: ElementConversion>(&self, fill_value: E) -> Self {
347 let dtype = self.dtype();
348 check!(TensorCheck::quantized_unsupported("Full Like", dtype));
349 Self::new(K::full(
350 self.shape(),
351 Scalar::new(fill_value, &dtype),
352 &self.device(),
353 dtype,
354 ))
355 }
356
357 /// Returns the dimensions of the current tensor.
358 ///
359 /// # Example
360 /// ```rust
361 /// use burn_tensor::Tensor;
362 ///
363 /// let device = Default::default();
364 /// let tensor = Tensor::<3>::ones([2, 3, 4], &device);
365 /// let dims = tensor.dims(); // [2, 3, 4]
366 /// println!("{dims:?}");
367 /// ```
368 pub fn dims(&self) -> [usize; D] {
369 Self::shape(self).dims()
370 }
371
372 /// Returns the shape of the current tensor.
373 ///
374 /// # Example
375 /// ```rust
376 /// use burn_tensor::Tensor;
377 ///
378 /// let device = Default::default();
379 /// let tensor = Tensor::<3>::ones([2, 3, 4], &device);
380 /// // Shape { dims: [2, 3, 4] }
381 /// let shape = tensor.shape();
382 /// ```
383 pub fn shape(&self) -> Shape {
384 self.primitive.shape()
385 }
386
387 /// Reshape the tensor to have the given shape.
388 ///
389 /// The tensor has the same data and number of elements as the input.
390 ///
391 /// A `-1` in the shape is used to infer the remaining dimensions, e.g.: `[2, -1]`
392 /// will reshape the tensor with [2, 3, 4] dimensions to [2, 12].
393 ///
394 /// A `0` in the shape instructs to keep the current dimension from the original tensor,
395 /// e.g.: `[2, 0, 4]` will reshape the tensor with [2, 3, 4] dimensions to [2, 3, 4].
396 /// This is useful when reshaping tensors with unknown dimensions and combining with `-1`
397 /// to infer the remaining dimensions, e.g. `[0, -1]` will reshape the tensor
398 /// with [1, 3, 4] dimensions to [1, 12].
399 ///
400 /// # Arguments
401 /// - `shape`: The new shape of the tensor.
402 ///
403 /// # Panics
404 /// - If the tensor contains more than one `-1` in the shape.
405 /// - If the tensor contains values that are not positive (other than -1).
406 /// - If the shape does not match the number of elements of the original shape.
407 ///
408 /// # Example
409 ///
410 /// ```rust
411 /// use burn_tensor::Tensor;
412 ///
413 /// let device = Default::default();
414 /// // Create a tensor with dimensions [2, 3, 4]
415 /// let tensor = Tensor::<3>::ones([2, 3, 4], &device);
416 /// // Reshape it to [2, 12], where 12 is inferred from the number of elements.
417 /// let reshaped = tensor.reshape([2, -1]);
418 /// println!("{reshaped}");
419 /// ```
420 pub fn reshape<const D2: usize, S: ReshapeArgs<D2>>(self, shape: S) -> Tensor<D2, K> {
421 // Convert reshape args to shape
422 let shape = shape.into_shape::<D2>(self.shape());
423 Tensor::new(K::reshape(self.primitive, shape))
424 }
425
426 /// Transpose the tensor.
427 ///
428 /// For a 2D tensor, this is the standard matrix transpose. For `D > 2`, the transpose is
429 /// applied on the last two dimensions. For example, the transpose of a tensor with shape
430 /// `[1, 2, 3, 4]` will have shape `[1, 2, 4, 3]`.
431 ///
432 /// See also [`permute`](Tensor::permute).
433 ///
434 /// # Arguments
435 ///
436 /// * `tensor` - The tensor to transpose.
437 ///
438 /// # Returns
439 ///
440 /// The transposed tensor.
441 ///
442 /// # Example
443 ///
444 /// ```rust
445 /// use burn_tensor::Tensor;
446 ///
447 /// let device = Default::default();
448 /// // Create a 2D tensor of shape [2, 3]
449 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
450 ///
451 /// // Transpose the tensor:
452 /// // [[1.0, 5.0], [-2.0, 9.0], [3.0, 6.0]]
453 /// // The resulting tensor will have dimensions [3, 2].
454 /// let transposed = tensor.transpose();
455 /// println!("{transposed}");
456 /// ```
457 pub fn transpose(self) -> Tensor<D, K> {
458 Tensor::new(K::transpose(self.primitive))
459 }
460
461 /// Alias for `transpose`.
462 #[inline(always)]
463 pub fn t(self) -> Tensor<D, K> {
464 self.transpose()
465 }
466
467 /// Swaps two dimensions of a tensor.
468 ///
469 /// This is a no-op when `dim1 == dim2`, assuming both are within bounds.
470 ///
471 /// # Arguments
472 ///
473 /// * `tensor` - The tensor to swap the dimensions of.
474 /// * `dim1` - The first dimension to swap, supports negative indexing.
475 /// * `dim2` - The second dimension to swap, supports negative indexing.
476 ///
477 /// # Returns
478 ///
479 /// The tensor with the dimensions swapped.
480 ///
481 /// # Panics
482 ///
483 /// When dimensions are out of bounds.
484 ///
485 /// # Example
486 ///
487 /// ```rust
488 /// use burn_tensor::Tensor;
489 ///
490 /// let device = Default::default();
491 /// // Create a 2D tensor of shape [2, 3]
492 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
493 ///
494 /// // Swap the dimensions 0 and -1 (equivalent to `tensor.transpose()`):
495 /// // [[1.0, 5.0], [-2.0, 9.0], [3.0, 6.0]]
496 /// // The resulting tensor will have dimensions [3, 2].
497 /// let swapped = tensor.swap_dims(0, -1);
498 /// println!("{swapped}");
499 /// ```
500 pub fn swap_dims<Dim1, Dim2>(self, dim1: Dim1, dim2: Dim2) -> Tensor<D, K>
501 where
502 Dim1: AsIndex,
503 Dim2: AsIndex,
504 {
505 let dim1 = unwrap_dim_index(dim1.try_dim_index(D), "Swap Dims");
506 let dim2 = unwrap_dim_index(dim2.try_dim_index(D), "Swap Dims");
507 if dim1 == dim2 {
508 self
509 } else {
510 Tensor::new(K::swap_dims(self.primitive, dim1, dim2))
511 }
512 }
513
514 /// Permute the dimensions of the tensor.
515 ///
516 /// This is a no-op when the resolved `axes` match the current order.
517 ///
518 /// # Arguments
519 ///
520 /// * `axes` - The new order of the dimensions. The length of the axes
521 /// must be equal to the number of dimensions of the tensor.
522 /// The values must be unique and in the range of the number of dimensions.
523 /// The values can be negative, in which case they are used as an offset from the end.
524 ///
525 /// # Returns
526 ///
527 /// The tensor with the dimensions permuted.
528 ///
529 /// # Example
530 ///
531 /// ```rust
532 /// use burn_tensor::Tensor;
533 ///
534 /// let device = Default::default();
535 /// // Create a 2D tensor of shape [3, 2]
536 /// let tensor = Tensor::<2>::from_data([[1.0, 5.0], [-2.0, 9.0], [3.0, 6.0]], &device);
537 ///
538 /// // Permute the dimensions 1 and 0:
539 /// // [[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]]
540 /// // The resulting tensor will have dimensions [3, 2].
541 /// let permuted = tensor.permute([1, 0]);
542 /// println!("{permuted}");
543 /// ```
544 pub fn permute<Dim>(self, axes: [Dim; D]) -> Tensor<D, K>
545 where
546 Dim: AsIndex,
547 {
548 let mut no_op = true;
549 let mut fixed_axes = [0; D];
550 for (i, axis) in axes.into_iter().enumerate() {
551 let dim = unwrap_dim_index(axis.try_dim_index(D), "Permute");
552 no_op &= dim == i;
553 fixed_axes[i] = dim;
554 }
555
556 if no_op {
557 self
558 } else {
559 check!(TensorCheck::permute(fixed_axes));
560 Tensor::new(K::permute(self.primitive, &fixed_axes))
561 }
562 }
563
564 /// Moves the dimension(s) of input at the position(s) in source to the position(s) in destination.
565 ///
566 /// Other dimensions of input that are not explicitly moved remain in their original order and appear
567 /// at the positions not specified in destination.
568 ///
569 /// # Arguments
570 ///
571 /// * `src` - The dimension(s) to move. The values must be unique and in the range of the number of dimensions.
572 /// The values can be negative, in which case they are used as an offset from the end.
573 ///
574 /// * `dst` - Destination positions for each of the original dims. These must also be unique.
575 /// Negative dimensions are counted from the end.
576 ///
577 /// # Panics
578 ///
579 /// - If the source and destination dimensions are not of the same length.
580 /// - If the source and destination vectors contain duplicate values.
581 /// - If the source and destination vectors contain values that are out of bounds.
582 ///
583 /// # Returns
584 ///
585 /// The tensor with the dimensions moved.
586 ///
587 /// # Example
588 ///
589 /// ```rust
590 /// use burn_tensor::Tensor;
591 ///
592 /// let device = Default::default();
593 /// // Create a 3D tensor of shape [3, 2, 1]
594 /// let tensor = Tensor::<3>::from_data([[[1.0], [5.0]], [[-2.0], [9.0]], [[3.0], [6.0]]], &device);
595 ///
596 /// // Move the dimensions 0 and 1:
597 /// // [[[1.0], [-2.0], [3.0]], [[5.0], [9.0], [6.0]]]
598 /// // The resulting tensor will have dimensions [2, 3, 1].
599 /// let moved = tensor.movedim(1, 0);
600 /// println!("{moved}");
601 /// ```
602 ///
603 /// # Note
604 ///
605 /// This is a syntactic sugar for `permute`. It is used widely enough, so we define a separate Op
606 /// for it
607 pub fn movedim<S1: MovedimArgs, S2: MovedimArgs>(self, src: S1, dst: S2) -> Tensor<D, K> {
608 let source_dims = src.into_dim_vec::<D>();
609 let destination_dims = dst.into_dim_vec::<D>();
610
611 check!(TensorCheck::movedim_args_length(
612 &source_dims,
613 &destination_dims
614 ));
615
616 let mut m = [-1; D];
617 for (&d, &s) in destination_dims.iter().zip(source_dims.iter()) {
618 m[d] = s as isize;
619 }
620 let mut axes: [isize; D] = [0; D];
621 let mut source_i = 0;
622 for (dest_i, item) in axes.iter_mut().enumerate().take(D) {
623 *item = if m[dest_i] != -1 {
624 m[dest_i]
625 } else {
626 while source_dims.contains(&source_i) {
627 source_i += 1;
628 }
629 let result = source_i as isize;
630 source_i += 1;
631 result
632 };
633 }
634
635 self.permute(axes)
636 }
637
638 /// Reverse the order of elements in the tensor along the given dimensions.
639 ///
640 /// # Arguments
641 ///
642 /// * `axes` - The dimensions to reverse. The values must be unique and in the range of the number of dimensions.
643 /// The values can be negative, in which case they are used as an offset from the end.
644 ///
645 /// # Returns
646 ///
647 /// The tensor with the axes flipped.
648 ///
649 /// # Example
650 ///
651 /// ```rust
652 /// use burn_tensor::Tensor;
653 ///
654 /// let device = Default::default();
655 /// // Create a 2D tensor with dimensions [4, 3]
656 /// let tensor = Tensor::<2>::from_data(
657 /// [
658 /// [3.0, 4.9, 2.0],
659 /// [2.0, 1.9, 3.0],
660 /// [4.0, 5.9, 8.0],
661 /// [1.4, 5.8, 6.0],
662 /// ],
663 /// &device,
664 /// );
665 ///
666 /// // Flip the elements in dimensions 0 and 1:
667 /// // [[6.0, 5.8, 1.4],
668 /// // [8.0, 5.9, 4.0],
669 /// // [3.0, 1.9, 2.0],
670 /// // [2.0, 4.9, 3.0]]
671 /// // The resulting tensor will have dimensions [4, 3].
672 /// let flipped = tensor.flip([0, 1]);
673 /// println!("{flipped}");
674 /// ```
675 pub fn flip<const N: usize>(self, axes: [impl AsIndex; N]) -> Tensor<D, K> {
676 // Convert the axes to usize without allocating.
677 let mut transformed_axes: [usize; N] = [0; N];
678 for (i, axis) in axes.into_iter().enumerate() {
679 transformed_axes[i] = unwrap_dim_index(axis.try_dim_index(D), "Flip");
680 }
681
682 // Check if the axes are valid
683 check!(TensorCheck::flip(D, &transformed_axes));
684
685 Tensor::new(K::flip(self.primitive, &transformed_axes))
686 }
687
688 /// Flatten the tensor along a given range of dimensions.
689 ///
690 /// This function collapses the specified range of dimensions into a single dimension,
691 /// effectively flattening the tensor in that range.
692 ///
693 /// # Arguments
694 ///
695 /// - `start_dim`: The starting dimension of the range to be flattened,
696 /// supports negative indexing.
697 /// - `end_dim`: The ending dimension of the range to be flattened (inclusive),
698 /// supports negative indexing.
699 ///
700 /// # Type Parameters
701 ///
702 /// - `D2`: The resulting number of dimensions in the flattened tensor.
703 ///
704 /// # Returns
705 ///
706 /// A new `Tensor<D2, K>` instance with the specified range of dimensions flattened.
707 ///
708 /// # Example
709 ///
710 /// ```rust
711 ///
712 /// use burn_tensor::{Tensor, Shape};
713 ///
714 /// let device = Default::default();
715 /// // Create a 3D tensor with dimensions [2, 3, 4]
716 /// let tensor = Tensor::<3>::ones(Shape::new([2, 3, 4]), &device);
717 ///
718 /// // Flatten the tensor from dimensions 1 to 2 (inclusive).
719 /// // The resulting tensor will have dimensions [2, 12]
720 /// let flattened: Tensor<2> = tensor.flatten(1, 2);
721 /// println!("{flattened}");
722 /// ```
723 pub fn flatten<const D2: usize>(
724 self,
725 start_dim: impl AsIndex,
726 end_dim: impl AsIndex,
727 ) -> Tensor<D2, K> {
728 let start_dim = unwrap_dim_index(start_dim.try_dim_index(D), "Flatten");
729 let end_dim = unwrap_dim_index(end_dim.try_dim_index(D), "Flatten");
730 check!(TensorCheck::flatten::<D, D2>(start_dim, end_dim));
731 let new_shape = self.shape().flatten_dims(start_dim, end_dim);
732
733 Tensor::new(K::reshape(self.primitive, new_shape))
734 }
735
736 /// Squeeze the tensor along all dimensions, removing dimensions
737 /// of size one, and effectively reducing the rank of the tensor.
738 ///
739 /// # Type Parameters
740 ///
741 /// - `D2`: The resulting number of dimensions in the squeezed tensor.
742 ///
743 /// # Returns
744 ///
745 /// A new `Tensor<D2, K>` instance with the specified dimension removed.
746 ///
747 /// # Example
748 ///
749 /// ```rust
750 ///
751 /// use burn_tensor::{Tensor, Shape};
752 ///
753 /// let device = Default::default();
754 /// // Create a 4D tensor with dimensions [1, 3, 1, 3]
755 /// let tensor = Tensor::<4>::from_data(
756 /// [[[[3.0, 4.9, 2.0]], [[2.0, 1.9, 3.0]], [[4.0, 5.9, 8.0]]]],
757 /// &device,
758 /// );
759 ///
760 /// // Squeeze the tensor dimensions.
761 /// // The resulting tensor will have dimensions [3, 3].
762 /// let squeezed = tensor.squeeze::<2>();
763 /// println!("{squeezed}");
764 /// ```
765 pub fn squeeze<const D2: usize>(self) -> Tensor<D2, K> {
766 let new_dims = self
767 .shape()
768 .iter()
769 .filter_map(|&dim| if dim == 1 { None } else { Some(dim) })
770 .collect::<Vec<_>>();
771 check!(TensorCheck::squeeze_dims_len::<D2>(new_dims.len()));
772
773 Tensor::new(K::reshape(self.primitive, new_dims.into()))
774 }
775
776 /// Squeeze the tensor along the given dimension, removing the specified dimension
777 /// of size one, and effectively reducing the rank of the tensor by one.
778 ///
779 /// # Arguments
780 ///
781 /// - `dim`: The dimension to be squeezed. Supports negative indexing.
782 ///
783 /// # Type Parameters
784 ///
785 /// - `D2`: The resulting number of dimensions in the squeezed tensor.
786 ///
787 /// # Panics
788 ///
789 /// If the size in the squeezed dimension is not 1.
790 ///
791 /// # Returns
792 ///
793 /// A new `Tensor<D2, K>` instance with the specified dimension removed.
794 ///
795 /// # Example
796 ///
797 /// ```rust
798 ///
799 /// use burn_tensor::{Tensor, Shape};
800 ///
801 /// let device = Default::default();
802 /// // Create a 3D tensor with dimensions [3, 1, 3]
803 /// let tensor = Tensor::<3>::from_data(
804 /// [[[3.0, 4.9, 2.0]], [[2.0, 1.9, 3.0]], [[4.0, 5.9, 8.0]]],
805 /// &device,
806 /// );
807 ///
808 /// // Squeeze the dimension 1.
809 /// // The resulting tensor will have dimensions [3, 3].
810 /// let squeezed = tensor.squeeze_dim::<2>(1);
811 /// println!("{squeezed}");
812 /// ```
813 pub fn squeeze_dim<const D2: usize>(self, dim: impl AsIndex) -> Tensor<D2, K> {
814 let dim = unwrap_dim_index(dim.try_dim_index(D), "Squeeze");
815 check!(TensorCheck::squeeze::<D2>(dim, &self.shape()));
816
817 let current_dims = self.shape();
818 let mut new_dims: [usize; D2] = [0; D2];
819
820 new_dims[..dim].copy_from_slice(¤t_dims[..dim]);
821 new_dims[dim..].copy_from_slice(¤t_dims[dim + 1..]);
822
823 check!(TensorCheck::squeeze_dims_len::<D2>(new_dims.len()));
824 Tensor::new(K::reshape(self.primitive, new_dims.into()))
825 }
826
827 /// Removes specified dimensions of size 1 from a tensor's shape. This function takes a tensor and
828 /// an array of dimensions (`dims`) to be squeezed. If `dims` is provided, only the dimensions
829 /// specified in this array will be removed. Each dimension in `dims` should correspond to a size of 1
830 /// in the tensor; otherwise, the dimension will not be squeezed. If `dims` is empty, all single-dimensional entries
831 /// in the tensor will be removed. If entries in `dims` are negative, then dimensions will be counted
832 /// from the back.
833 ///
834 /// # Arguments
835 ///
836 /// - `dims`: The dimension(s) to be squeezed.
837 ///
838 /// # Type Parameters
839 ///
840 /// - `D2`: The resulting number of dimensions in the squeezed tensor.
841 ///
842 /// # Returns
843 ///
844 /// A new `Tensor<D2, K>` instance with the specified dimensions removed.
845 ///
846 /// # Example
847 ///
848 /// ```rust
849 ///
850 /// use burn_tensor::{Tensor, Shape};
851 ///
852 /// let device = Default::default();
853 /// // Create a 4D tensor with dimensions [2, 1, 4, 1]
854 /// let tensor = Tensor::<4>::ones(Shape::new([2, 1, 4, 1]), &device);
855 ///
856 /// // Squeeze the dimensions 1 and 3.
857 /// // The resulting tensor will have dimensions [2, 4].
858 /// let squeezed: Tensor<2> = tensor.squeeze_dims(&[1, 3]);
859 /// println!("{squeezed}");
860 /// ```
861 pub fn squeeze_dims<const D2: usize>(self, dims: &[impl AsIndex]) -> Tensor<D2, K> {
862 let current_dims = self.shape();
863 let mut dim_indices: Vec<usize>;
864
865 // Check if dims is empty, if yes then assign dim_indices all single-dimensional entries
866 if dims.is_empty() {
867 dim_indices = current_dims
868 .iter()
869 .enumerate()
870 .filter_map(|(index, &dim)| if dim == 1 { Some(index) } else { None })
871 .collect();
872 } else {
873 dim_indices = dims
874 .iter()
875 .map(|dim| unwrap_dim_index(dim.try_dim_index(D), "Squeeze"))
876 .collect();
877 }
878
879 // Sort indices and remove duplicates
880 dim_indices.sort_unstable();
881 dim_indices.dedup();
882
883 // Make sure squeeze_dims doesn't result in a tensor with < 1 dimensions
884 check!(TensorCheck::squeeze_dims_input::<D2>(
885 &dim_indices,
886 ¤t_dims
887 ));
888
889 // Calculate new dimensions
890 let mut new_dims = Vec::new();
891 for (index, &dim_size) in current_dims.iter().enumerate() {
892 // Exclude the dimension if it's explicitly marked for squeezing
893 if dim_indices.contains(&index) {
894 check!(TensorCheck::squeeze::<D2>(index, ¤t_dims));
895 continue;
896 }
897 new_dims.push(dim_size);
898 }
899
900 // Check that after squeezing, we still respect the D2 size
901 check!(TensorCheck::squeeze_dims_len::<D2>(new_dims.len()));
902
903 Tensor::new(K::reshape(self.primitive, new_dims.into()))
904 }
905
906 /// Unsqueeze the current tensor. Create new leading dimensions to fit the given size.
907 ///
908 /// # Type Parameters
909 ///
910 /// - `D2`: The resulting number of dimensions in the unsqueezed tensor.
911 ///
912 /// # Panics
913 ///
914 /// If the output size `D2` is smaller than the current number of dimensions.
915 ///
916 /// # Returns
917 ///
918 /// A new `Tensor<D2, K>` instance with the specified dimensions added.
919 ///
920 /// # Example
921 ///
922 /// ```rust
923 /// use burn_tensor::{Tensor, Shape};
924 ///
925 /// let device = Default::default();
926 /// // Create a 2D tensor with dimensions [3, 3]
927 /// let tensor = Tensor::<2>::ones(Shape::new([3, 3]), &device);
928 /// // Unsqueeze the tensor up to 4 dimensions.
929 /// // The resulting tensor will have dimensions [1, 1, 3, 3].
930 /// let unsqueezed = tensor.unsqueeze::<4>();
931 /// println!("{unsqueezed}");
932 /// ```
933 pub fn unsqueeze<const D2: usize>(self) -> Tensor<D2, K> {
934 check!(TensorCheck::unsqueeze::<D, D2>());
935
936 let mut dims = [1; D2];
937 let num_ones = D2 - D;
938 let shape = self.shape();
939
940 dims[num_ones..(D + num_ones)].copy_from_slice(&shape[..D]);
941
942 let shape = Shape::new(dims);
943 self.reshape(shape)
944 }
945
946 /// Creates a new tensor with a dimension of size one inserted at the specified position.
947 ///
948 /// Negative dimensions are counted from the end of the valid insertion positions.
949 ///
950 /// # Example
951 ///
952 /// ```rust
953 /// use burn_tensor::{Tensor, Shape};
954 ///
955 /// let device = Default::default();
956 /// // Create a 2D tensor with dimensions [3, 3]
957 /// let tensor = Tensor::<2>::ones(Shape::new([3, 3]), &device);
958 /// // Unsqueeze the dimension 1.
959 /// // The resulting tensor will have dimensions [3, 1, 3].
960 /// let unsqueezed: Tensor<3> = tensor.unsqueeze_dim(1);
961 /// println!("{unsqueezed}");
962 /// ```
963 pub fn unsqueeze_dim<const D2: usize>(self, dim: impl AsIndex) -> Tensor<D2, K> {
964 let dim = unwrap_dim_index(dim.try_dim_index(D + 1), "Unsqueeze");
965 check!(TensorCheck::unsqueeze_dim::<D, D2>(dim));
966
967 let mut dims = [1; D2];
968 let shape = self.shape();
969
970 dims[0..dim].copy_from_slice(&shape[0..dim]);
971
972 if dim < D {
973 dims[dim] = 1;
974 dims[(dim + 1)..(D + 1)].copy_from_slice(&shape[dim..]);
975 } else {
976 dims[dim] = 1;
977 }
978
979 let shape = Shape::new(dims);
980 self.reshape(shape)
981 }
982
983 /// Creates a new tensor with added dimensions of size one inserted at the specified indices.
984 /// The indices can be negative, in which case they are counted from the last to the first dimension.
985 /// the axes can contain duplicates, in which case the number of dimensions inserted at the index
986 /// is the number of duplicates.
987 /// # Example
988 ///
989 /// ```rust
990 /// use burn_tensor::{Tensor, Shape};
991 ///
992 /// let device = Default::default();
993 /// // Create a 3D tensor with dimensions [3, 4, 5]
994 /// let tensor = Tensor::<3>::ones(Shape::new([3, 4, 5]), &device);
995 /// // Unsqueeze the leading dimension (0) once and the trailing dimension (-1) twice.
996 /// // The resulting tensor will have dimensions [1, 3, 4, 5, 1, 1].
997 /// let unsqueezed: Tensor<6> = tensor.unsqueeze_dims(&[0, -1, -1]);
998 /// println!("{unsqueezed}");
999 /// ```
1000 pub fn unsqueeze_dims<const D2: usize>(self, axes: &[impl AsIndex]) -> Tensor<D2, K> {
1001 let mut new_dims = [1; D2];
1002 let old_dims = self.shape();
1003 //for checking if the dimension is in the acceptable range
1004
1005 //part 1: convert the negative indices to positive
1006 let mut neg_offset = D2;
1007 let mut dim_indices = axes
1008 .iter()
1009 .map(|d| {
1010 let d = d.as_index();
1011 // check if the dimension is in the acceptable range
1012 check!(TensorCheck::unsqueeze_dims::<{ D2 }>(d));
1013 (if d < 0 {
1014 neg_offset -= 1; // handle multiple negative indices (decrease dim value in reverse)
1015 d + neg_offset as isize + 1
1016 } else {
1017 d
1018 }) as usize
1019 })
1020 .collect::<Vec<usize>>();
1021
1022 //sort the indices
1023 dim_indices.sort_unstable();
1024
1025 // Per the documented semantics, duplicate axes mean "insert N dims at that index".
1026 // After sorting, N insertions at position `i` logically occupy positions
1027 // `i, i+1, ..., i+N-1` in the output, so bump each duplicate to the next slot.
1028 // Example: sorted `[0, 0, 3]` becomes `[0, 1, 3]`, matching the intent of
1029 // "two 1s starting at index 0, plus one 1 at index 3".
1030 for i in 1..dim_indices.len() {
1031 if dim_indices[i] <= dim_indices[i - 1] {
1032 dim_indices[i] = dim_indices[i - 1] + 1;
1033 }
1034 }
1035
1036 // Re-validate after normalization: bumping duplicates forward can push the
1037 // last index past `D2 - 1` (e.g. `[2, 2]` targeting rank 3 normalizes to
1038 // `[2, 3]`). The per-axis check above only runs on pre-normalization values,
1039 // so we re-check here to surface a clear `TensorCheck` error instead of
1040 // letting the copy loop panic on an out-of-bounds `old_dims` read.
1041 for &dim_index in &dim_indices {
1042 check!(TensorCheck::unsqueeze_dims::<{ D2 }>(dim_index as isize));
1043 }
1044
1045 // Loop over the entries/indices of the `new_dims` array.
1046 // When the current entry should be 1 from the unsqueeze operation, simply increment
1047 // the index for `dims_indices` to account for "adding" its entry to `new_dims`.
1048 // Otherwise, the dim from the current entry of `old_dims` should be copied to `new_dims`.
1049 let mut dim_indices_curr_idx = 0;
1050 let mut old_dims_curr_idx = 0;
1051 for new_dims_curr_idx in 0..D2 {
1052 // If all indices in `dim_indices` have been processed, then
1053 // simply copy all the remaining dims from `old_dims` to `new_dims`
1054 if dim_indices_curr_idx == dim_indices.len() {
1055 new_dims[new_dims_curr_idx..].copy_from_slice(&old_dims[old_dims_curr_idx..]);
1056 break;
1057 }
1058
1059 if new_dims_curr_idx == dim_indices[dim_indices_curr_idx] {
1060 dim_indices_curr_idx += 1;
1061 } else {
1062 new_dims[new_dims_curr_idx] = old_dims[old_dims_curr_idx];
1063 old_dims_curr_idx += 1;
1064 }
1065 }
1066
1067 //lastly, create the shape and reshape
1068 let shape = Shape::new(new_dims);
1069 self.reshape(shape)
1070 }
1071
1072 /// Roll operation along a specific dimension; wrapping around the elements.
1073 ///
1074 /// ## Parameters
1075 ///
1076 /// - `shift`: The roll extent; supports negative values and wraps around.
1077 /// - `dim`: The dimension to roll; supports negative indexing.
1078 ///
1079 /// ## Returns
1080 ///
1081 /// A new tensor with the specified dimension rolled by the given shift amount.
1082 pub fn roll_dim<Shift, Dim>(self, shift: Shift, dim: Dim) -> Self
1083 where
1084 Shift: AsIndex,
1085 Dim: AsIndex,
1086 {
1087 let dim = unwrap_dim_index(dim.try_dim_index(D), "Roll");
1088 let size = self.shape()[dim];
1089 if size == 0 {
1090 // If the dimension is empty, return the tensor as is.
1091 return self;
1092 }
1093
1094 let shift = wrap_index(shift, size);
1095 if shift == 0 {
1096 // If the shift is zero, return the tensor as is.
1097 return self;
1098 }
1099
1100 self.unchecked_roll_dim(shift, dim)
1101 }
1102
1103 /// Internal implementation of `roll_dim` that does not canonicalize dimensions or shifts.
1104 ///
1105 /// ## Parameters
1106 ///
1107 /// - `shift`: The number of positions to shift; must be (0 < shift < size).
1108 /// - `dim`: The dimension to roll; must be a valid index for the tensor's shape.
1109 ///
1110 /// ## Returns
1111 ///
1112 /// A new tensor with the specified dimension rolled by the given shift amount.
1113 #[inline(always)]
1114 fn unchecked_roll_dim(self, shift: usize, dim: usize) -> Self {
1115 #[cfg(debug_assertions)]
1116 {
1117 let size = self.shape()[dim];
1118 assert!(
1119 0 < shift && shift < size,
1120 "Expected: 0 < shift < size: found shift={shift}, size={size}",
1121 );
1122 assert!(
1123 dim < self.shape().num_dims(),
1124 "Expected: dim < num_dims: found dim={dim}, num_dims={size}",
1125 );
1126 }
1127
1128 let split = self.shape()[dim] - shift;
1129 Tensor::cat(
1130 vec![
1131 self.clone().slice_dim(dim, split..),
1132 self.slice_dim(dim, ..split),
1133 ],
1134 dim,
1135 )
1136 }
1137
1138 /// Roll operation.
1139 ///
1140 /// Note: unlike ``pytorch``, `dims` and `shifts` must have the same length.
1141 ///
1142 /// A given `dim` may be rolled multiple times, and the shifts will be applied sequentially.
1143 ///
1144 /// ## Parameters
1145 ///
1146 /// - `shifts`: A slice of shifts corresponding to each dimension;
1147 /// supports negative values and wraps around.
1148 /// - `dims`: A slice of dimensions to roll; supports negative indexing.
1149 ///
1150 /// ## Returns
1151 ///
1152 /// A new tensor with the specified dimensions rolled by the given shifts.
1153 pub fn roll<Shift, Dim>(self, shifts: &[Shift], dims: &[Dim]) -> Self
1154 where
1155 Shift: AsIndex,
1156 Dim: AsIndex,
1157 {
1158 assert_eq!(
1159 dims.len(),
1160 shifts.len(),
1161 "Dimensions and shifts must align; found dims={dims:#?}, shifts={shifts:#?}",
1162 );
1163
1164 // This is a fair amount of complexity, which could be replaced
1165 // by a simple canonicalization of `dims` and wrapping of `shifts`.
1166 // The work is done here to ensure that any roll operation
1167 // which could be a no-op is a no-op; simplifying the accounting
1168 // needed by backend-specific implementations of the inner roll op.
1169
1170 let item_count = dims.len();
1171
1172 let shape = self.shape();
1173
1174 // Accumulate the effective shifts for each dimension.
1175 let mut accumulated_shifts: Vec<isize> = vec![0; shape.len()];
1176 for i in 0..item_count {
1177 let dim = unwrap_dim_index(dims[i].try_dim_index(D), "Roll");
1178 accumulated_shifts[dim] += shifts[i].as_index();
1179 }
1180
1181 // Do this after we've checked the validity of `dims` and `shifts`.
1182 if self.shape().num_elements() == 0 {
1183 // If the tensor is empty, return it as is.
1184 return self;
1185 }
1186
1187 // Wrap the accumulated shifts, and filter out empty dimensions.
1188 let mut effective_dims: Vec<usize> = Vec::with_capacity(item_count);
1189 let mut effective_shifts: Vec<usize> = Vec::with_capacity(item_count);
1190 for dim in 0..shape.len() {
1191 // `wrap_index` should inline, and has a fast-exit path for zero shifts.
1192 let shift = wrap_index(accumulated_shifts[dim], shape[dim]);
1193 if shift == 0 {
1194 continue;
1195 }
1196
1197 effective_dims.push(dim);
1198 effective_shifts.push(shift);
1199 }
1200
1201 // If no shifts are needed, return the original tensor.
1202 if effective_shifts.is_empty() {
1203 return self;
1204 }
1205
1206 // At this point:
1207 // - `dims` contains the effective dimensions to roll, in index order,
1208 // - `shifts` contains the effective usize shifts for each dimension.
1209 // - Every shift is non-zero, and less than the size of the corresponding dimension.
1210 self.unchecked_roll(&effective_shifts, &effective_dims)
1211 }
1212
1213 /// `roll` internal implementation.
1214 ///
1215 /// ## Parameters
1216 ///
1217 /// - `shifts`: A slice of shifts corresponding to each dimension;
1218 /// must be non-empty, the same length as `dims`, and all ``1..<size>``.
1219 /// - `dims`: A slice of dimensions to roll; must be non-empty;
1220 /// the same length as `shifts`, and must not contain repeats.
1221 ///
1222 /// ## Panics
1223 ///
1224 /// Panics if the shifts and dimensions do not align, or if dimensions contain repeats.
1225 ///
1226 /// ## Returns
1227 ///
1228 /// A new tensor with the specified dimensions rolled by the given shifts.
1229 #[inline(always)]
1230 fn unchecked_roll(self, shifts: &[usize], dims: &[usize]) -> Self {
1231 #[cfg(debug_assertions)]
1232 {
1233 assert!(!shifts.is_empty());
1234 assert_eq!(
1235 shifts.len(),
1236 dims.len(),
1237 "Shifts and dimensions must align; found {} shifts and {} dims",
1238 shifts.len(),
1239 dims.len()
1240 );
1241
1242 let mut unique_dims = dims.to_vec();
1243 unique_dims.dedup();
1244
1245 assert_eq!(
1246 unique_dims.len(),
1247 dims.len(),
1248 "Dimensions must not contain repeats; found {} unique dims and {} total dims",
1249 unique_dims.len(),
1250 dims.len()
1251 )
1252 }
1253
1254 let x = self.unchecked_roll_dim(shifts[0], dims[0]);
1255
1256 if dims.len() == 1 {
1257 x
1258 } else {
1259 x.unchecked_roll(&shifts[1..], &dims[1..])
1260 }
1261 }
1262
1263 /// Returns a tensor containing the elements selected from the given slices.
1264 ///
1265 /// This method provides flexible tensor slicing with support for various range types,
1266 /// negative indices, and stepped slicing. The method accepts both single slices and
1267 /// arrays of slices, with the [`s!`] macro providing convenient syntax for complex patterns.
1268 ///
1269 /// # Arguments
1270 ///
1271 /// * `slices` - Can be:
1272 /// - A single range for 1D slicing (e.g., `0..5`, `..`, `2..`)
1273 /// - An array of ranges (e.g., `[0..2, 1..4]`)
1274 /// - The [`s!`] macro output for advanced slicing with steps
1275 /// - a `&Vec<Slice>` or `&[Slice]`
1276 ///
1277 /// # Behavior
1278 ///
1279 /// - Supports partial and full slicing in any number of dimensions
1280 /// - Handles negative indices by wrapping from the end (-1 is the last element)
1281 /// - Automatically clamps ranges that exceed tensor dimensions
1282 /// - Supports stepped slicing for selecting every nth element
1283 /// - Negative steps reverse the selection order
1284 ///
1285 /// # Panics
1286 ///
1287 /// - If the number of slices exceeds the tensor's dimensions
1288 /// - If a range is descending (e.g., 2..1) or empty (e.g., 1..1) without negative step
1289 /// - If a step is zero
1290 ///
1291 /// # Examples
1292 ///
1293 /// ```rust
1294 /// use burn_tensor::{Tensor, Shape, s};
1295 ///
1296 /// let device = Default::default();
1297 ///
1298 /// // Single dimension slicing - no brackets needed!
1299 /// let tensor = Tensor::<1, burn_tensor::Int>::arange(0..10, &device);
1300 /// let slice = tensor.clone().slice(2..8); // Simple range
1301 /// assert_eq!(slice.try_into_vec_as::<i32>().unwrap(), vec![2, 3, 4, 5, 6, 7]);
1302 ///
1303 /// // Using s! macro for single dimension with step
1304 /// let slice = tensor.clone().slice(s![0..10;2]); // Every 2nd element
1305 /// assert_eq!(slice.try_into_vec_as::<i32>().unwrap(), vec![0, 2, 4, 6, 8]);
1306 ///
1307 /// // Reverse a dimension with negative step
1308 /// let slice = tensor.slice(s![..;-1]); // Reverse entire tensor
1309 /// assert_eq!(slice.try_into_vec_as::<i32>().unwrap(), vec![9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
1310 ///
1311 /// // Multi-dimensional slicing
1312 /// let tensor = Tensor::<2>::ones(Shape::new([4, 6]), &device);
1313 ///
1314 /// // Array syntax for simple ranges
1315 /// let slice = tensor.clone().slice([1..3, 2..5]);
1316 /// assert_eq!(slice.dims(), [2, 3]);
1317 ///
1318 /// // Advanced multi-dimensional with s! macro
1319 /// let slice = tensor.clone().slice(s![0..4;2, ..;-1]); // Every 2nd row, reverse columns
1320 /// assert_eq!(slice.dims(), [2, 6]);
1321 ///
1322 /// // Complex 3D example with mixed slice types
1323 /// let tensor = Tensor::<3>::ones(Shape::new([4, 6, 8]), &device);
1324 /// let slice = tensor.slice(s![1..3, ..;2, -3..]); // Rows 1-2, every 2nd col, last 3 depth
1325 /// assert_eq!(slice.dims(), [2, 3, 3]);
1326 ///
1327 /// // Using negative indices
1328 /// let tensor = Tensor::<2>::ones(Shape::new([4, 6]), &device);
1329 /// let slice = tensor.slice(s![-2.., ..-1]); // Last 2 rows, all but last column
1330 /// assert_eq!(slice.dims(), [2, 5]);
1331 /// ```
1332 ///
1333 /// # See Also
1334 ///
1335 /// - [`s!`] - The recommended macro for creating complex slice specifications
1336 /// - [`slice_assign`](Self::slice_assign) - Assign values to a slice
1337 /// - [`slice_fill`](Self::slice_fill) - Fill a slice with a constant value
1338 /// - [`slice_dim`](Self::slice_dim) - Slice a single dimension
1339 ///
1340 /// [`s!`]: crate::s!
1341 pub fn slice<S>(self, slices: S) -> Self
1342 where
1343 S: SliceArg,
1344 {
1345 let shape = self.shape();
1346 let slices = slices.into_slices(&shape);
1347
1348 // Validate slices
1349 check!(TensorCheck::slice::<D>(&shape, &slices));
1350
1351 // Calculate output shape and check for empty slices
1352 let mut output_dims = shape.clone();
1353 for (dim, slice) in slices.iter().enumerate() {
1354 output_dims[dim] = slice.output_size(shape[dim]);
1355 }
1356
1357 // Return empty tensor if any dimension is 0 (empty slice)
1358 if output_dims.contains(&0) {
1359 return Self::new(K::empty(output_dims, &self.device(), self.creation_dtype()));
1360 }
1361 Self::new(K::slice(self.primitive, &slices))
1362 }
1363
1364 /// Assigns values to a slice of the tensor and returns the updated tensor.
1365 ///
1366 /// This method supports advanced slicing with steps, including negative steps for reverse
1367 /// assignment. Like `slice`, it accepts both single slices and arrays, with the [`s!`] macro
1368 /// providing powerful syntax for complex patterns.
1369 ///
1370 /// # Arguments
1371 ///
1372 /// * `slices` - Slice specification (same format as `slice` method)
1373 /// * `values` - Tensor with values to assign (must match slice dimensions)
1374 ///
1375 /// # Panics
1376 ///
1377 /// - If slices exceed tensor dimensions
1378 /// - If values dimensions don't match the selected slice shape
1379 /// - If a step is zero
1380 ///
1381 /// # Examples
1382 ///
1383 /// ```rust
1384 /// use burn_tensor::{Tensor, s};
1385 ///
1386 /// let device = Default::default();
1387 ///
1388 /// // Simple assignment to a sub-region
1389 /// let mut tensor = Tensor::<2>::zeros([4, 6], &device);
1390 /// let values = Tensor::<2>::ones([2, 3], &device);
1391 /// tensor = tensor.slice_assign([1..3, 2..5], values);
1392 /// // Now tensor[1..3, 2..5] contains ones
1393 ///
1394 /// // Single dimension assignment with step
1395 /// let mut tensor = Tensor::<1>::zeros([10], &device);
1396 /// let values = Tensor::<1>::ones([5], &device);
1397 /// tensor = tensor.slice_assign(s![0..10;2], values);
1398 /// // Now every 2nd element is 1: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
1399 ///
1400 /// // Reverse assignment with negative step
1401 /// let mut tensor = Tensor::<1>::from_data([0.0, 1.0, 2.0, 3.0, 4.0], &device);
1402 /// let values = Tensor::<1>::from_data([10.0, 11.0, 12.0, 13.0, 14.0], &device);
1403 /// tensor = tensor.slice_assign(s![..;-1], values);
1404 /// // Assigns in reverse: [14, 13, 12, 11, 10]
1405 ///
1406 /// // Complex multi-dimensional assignment
1407 /// let mut tensor = Tensor::<3>::zeros([4, 6, 8], &device);
1408 /// let values = Tensor::<3>::ones([2, 3, 3], &device);
1409 /// tensor = tensor.slice_assign(s![0..4;2, ..;2, -3..], values);
1410 /// // Assigns to every 2nd row, every 2nd column, last 3 in depth
1411 ///
1412 /// // Mixed syntax example
1413 /// let mut tensor = Tensor::<2>::zeros([8, 8], &device);
1414 /// let pattern = Tensor::<2>::ones([4, 4], &device);
1415 /// tensor = tensor.slice_assign(s![..;2, ..;2], pattern);
1416 /// // Creates a checkerboard pattern with ones
1417 /// ```
1418 ///
1419 /// # See Also
1420 ///
1421 /// - [`s!`] - The recommended macro for creating complex slice specifications
1422 /// - [`slice`](Self::slice) - Extract a slice from a tensor
1423 /// - [`slice_fill`](Self::slice_fill) - Fill a slice with a constant value
1424 ///
1425 /// [`s!`]: crate::s!
1426 pub fn slice_assign<S>(self, slices: S, values: Self) -> Self
1427 where
1428 S: SliceArg,
1429 {
1430 let shape = self.shape();
1431 let slices = slices.into_slices(&shape);
1432
1433 // Check if any slice produces 0 elements (empty assignment).
1434 // Empty assignments are no-ops and would cause issues in backend implementations.
1435 let is_empty_assignment = slices
1436 .iter()
1437 .enumerate()
1438 .any(|(i, slice)| slice.output_size(shape[i]) == 0);
1439
1440 if is_empty_assignment {
1441 return self;
1442 }
1443
1444 check!(TensorCheck::slice_assign::<D>(
1445 &shape,
1446 &values.shape(),
1447 &slices
1448 ));
1449
1450 Self::new(K::slice_assign(self.primitive, &slices, values.primitive))
1451 }
1452
1453 /// Fills a slice of the tensor with a constant value and returns the updated tensor.
1454 ///
1455 /// Like other slice methods, accepts both single slices and arrays. However, this method
1456 /// currently **does not support stepped slicing** - use [`slice_assign`](Self::slice_assign)
1457 /// with a constant tensor for stepped patterns.
1458 ///
1459 /// # Arguments
1460 ///
1461 /// * `slices` - Slice specification (same format as `slice` method, but no steps)
1462 /// * `value` - The value to fill the slice with
1463 ///
1464 /// # Panics
1465 ///
1466 /// - If slices exceed tensor dimensions
1467 /// - If any slice has a step != 1 (not yet supported)
1468 ///
1469 /// # Examples
1470 ///
1471 /// ```rust
1472 /// use burn_tensor::{Tensor, s};
1473 ///
1474 /// let device = Default::default();
1475 ///
1476 /// // Simple fill for a single dimension
1477 /// let mut tensor = Tensor::<1>::zeros([10], &device);
1478 /// tensor = tensor.slice_fill(2..5, 1.0);
1479 /// // Now tensor is [0, 0, 1, 1, 1, 0, 0, 0, 0, 0]
1480 ///
1481 /// // Multi-dimensional fill
1482 /// let mut tensor = Tensor::<2>::zeros([4, 6], &device);
1483 /// tensor = tensor.slice_fill([1..3, 2..5], -1.0);
1484 /// // Fills the rectangle at rows 1-2, columns 2-4 with -1
1485 ///
1486 /// // Using negative indices
1487 /// let mut tensor = Tensor::<1>::zeros([10], &device);
1488 /// tensor = tensor.slice_fill(-3.., 2.0);
1489 /// // Fills the last 3 elements with 2.0
1490 ///
1491 /// // Complex multi-dimensional example
1492 /// let mut tensor = Tensor::<3>::ones([4, 6, 8], &device);
1493 /// tensor = tensor.slice_fill(s![1..3, .., -2..], 0.0);
1494 /// // Sets rows 1-2, all columns, last 2 in depth to 0
1495 ///
1496 /// // Stepped slicing is supported
1497 /// let mut tensor = Tensor::<1>::zeros([10], &device);
1498 /// tensor = tensor.slice_fill(s![0..10;2], 1.0);
1499 /// // Now every 2nd element is 1: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
1500 /// ```
1501 ///
1502 /// # See Also
1503 ///
1504 /// - [`s!`] - The macro for creating slice specifications with steps
1505 /// - [`slice`](Self::slice) - Extract a slice from a tensor
1506 /// - [`slice_assign`](Self::slice_assign) - Assign tensor values to a slice
1507 ///
1508 /// [`s!`]: crate::s!
1509 pub fn slice_fill<S, E: Element>(self, slices: S, value: E) -> Self
1510 where
1511 S: SliceArg,
1512 {
1513 let shape = self.shape();
1514 let slices = slices.into_slices(&shape);
1515
1516 check!(TensorCheck::slice::<D>(&shape, &slices));
1517
1518 let slice_shape = shape.slice(&slices).unwrap();
1519 let value = Tensor::<1, K>::from_data([value], (&self.device(), self.dtype()));
1520 let value = value.expand(slice_shape);
1521 self.slice_assign(&slices, value)
1522 }
1523
1524 /// Returns a new tensor with the specified dimension sliced.
1525 ///
1526 /// # Arguments
1527 ///
1528 /// * `dim`: The dimension to slice. Supports negative indexing.
1529 /// * `slice`: The slice specification for the dimension. Can be a range (e.g., `2..5`),
1530 /// slice with step (via `s!` macro, e.g., `s![0..10;2]`), or any type that implements `Into<Slice>`.
1531 ///
1532 /// # Returns
1533 ///
1534 /// A new tensor with the specified dimension sliced.
1535 ///
1536 /// # Panics
1537 ///
1538 /// If the slice is out of bounds for the specified dimension.
1539 ///
1540 /// # Examples
1541 ///
1542 /// ```rust
1543 /// # use burn_tensor::{Tensor, s};
1544 /// #
1545 /// # fn example() {
1546 /// # let device = Default::default();
1547 /// let tensor = Tensor::<3>::zeros([3, 4, 5], &device);
1548 ///
1549 /// // Simple range slicing
1550 /// let sliced = tensor.clone().slice_dim(1, 1..3);
1551 /// assert_eq!(sliced.shape().as_slice(), [3, 2, 5]);
1552 ///
1553 /// // Slicing with step - take every 2nd element
1554 /// let sliced = tensor.clone().slice_dim(2, s![0..5;2]);
1555 /// assert_eq!(sliced.shape().as_slice(), [3, 4, 3]); // Takes indices 0, 2, 4
1556 ///
1557 /// // Reverse slicing with negative step
1558 /// let sliced = tensor.clone().slice_dim(1, s![..;-1]);
1559 /// assert_eq!(sliced.shape().as_slice(), [3, 4, 5]); // Reverses dimension 1
1560 ///
1561 /// // Select from index 2 with step 3
1562 /// let sliced = tensor.clone().slice_dim(0, s![2..;3]);
1563 /// assert_eq!(sliced.shape().as_slice(), [1, 4, 5]); // Takes only index 2
1564 ///
1565 /// // Select single index (reduces dimension to size 1)
1566 /// let sliced = tensor.slice_dim(0, 1);
1567 /// assert_eq!(sliced.shape().as_slice(), [1, 4, 5]);
1568 /// # }
1569 /// ```
1570 ///
1571 /// # See Also
1572 ///
1573 /// - [`slice`](Self::slice) - Slice multiple dimensions simultaneously
1574 /// - [`s!`] - The macro for creating complex slice specifications
1575 ///
1576 /// [`s!`]: crate::s!
1577 pub fn slice_dim<S>(self, dim: impl AsIndex, slice: S) -> Self
1578 where
1579 S: Into<Slice>,
1580 {
1581 let dim = unwrap_dim_index(dim.try_dim_index(D), "Slice");
1582 let slice: Slice = slice.into();
1583
1584 let mut slices = vec![Slice::full(); D];
1585 slices[dim] = slice;
1586
1587 self.slice(&slices)
1588 }
1589
1590 /// Returns a new tensor selecting a dimension index, and then squeezing that dim.
1591 ///
1592 /// This is defined as equivalent to `t.slice_dim(dim, index).squeeze_dim::<D2>(dim)`
1593 ///
1594 /// # Arguments
1595 /// * `dim`: The dimension to slice. Supports negative indexing.
1596 /// * `index`: the dimension index. Supports negative indexing.
1597 ///
1598 /// # Example
1599 /// ```rust
1600 /// use burn_tensor::{Tensor, TensorData, s};
1601 ///
1602 /// let device = Default::default();
1603 /// let tensor = Tensor::<2>::from_data(
1604 /// [
1605 /// [1.0, 2.0, 3.0],
1606 /// [4.0, 5.0, 6.0],
1607 /// ],
1608 /// &device,
1609 /// );
1610 ///
1611 /// let row1 : Tensor<1> = tensor.clone().select_dim(0, 1);
1612 /// row1
1613 /// .to_data()
1614 /// .assert_eq(&TensorData::from([4.0, 5.0, 6.0]), false);
1615 ///
1616 /// let col1 : Tensor<1> = tensor.clone().select_dim(1, 1);
1617 /// col1
1618 /// .to_data()
1619 /// .assert_eq(&TensorData::from([2.0, 5.0]), false);
1620 /// ```
1621 pub fn select_dim<const D2: usize>(
1622 self,
1623 dim: impl AsIndex,
1624 index: impl AsIndex,
1625 ) -> Tensor<D2, K> {
1626 let index = index.as_index();
1627 self.slice_dim(dim, index).squeeze_dim(dim)
1628 }
1629
1630 /// Returns the device of the current tensor.
1631 pub fn device(&self) -> Device {
1632 K::device(&self.primitive)
1633 }
1634
1635 /// Moves the tensor to the target device's compute resource.
1636 ///
1637 /// This operation preserves the tensor's autodiff association and gradient-checkpointing
1638 /// strategy; the target device's autodiff configuration isn't applied. Tensors newly created
1639 /// on a device inherit its configuration, while tensors moved to that device retain their
1640 /// existing one. Change it explicitly with [`autodiff`](Tensor::autodiff),
1641 /// [`without_autodiff`](Tensor::without_autodiff), or
1642 /// `Tensor::with_gradient_checkpointing_strategy`.
1643 ///
1644 /// For tracked floating-point tensors, `to_device` is a recorded operation, including when
1645 /// the target is the current device. Its output is a non-leaf tensor and cannot retain its
1646 /// own gradient: calling `require_grad()` on it panics. To create a new leaf on the destination,
1647 /// use `tensor.to_device(device).detach().require_grad()`, which severs the source connection.
1648 /// Supported transfers between compute backends preserve the graph connection; backward
1649 /// transfers gradients to the original source device.
1650 /// Distributed backward currently requires every distributed parameter to use the same
1651 /// backend as the loss. Incompatible graphs panic before synchronization or gradient
1652 /// computation begins.
1653 ///
1654 /// Transfers between different compute backends currently read values into host memory as
1655 /// [`TensorData`] and upload them to the destination backend. This also applies to gradients
1656 /// transferred during backward and can incur synchronization and data-copy overhead.
1657 ///
1658 /// # Panics
1659 ///
1660 /// Panics when the backend doesn't support the requested transfer, including transfers to a
1661 /// capture device with autodiff enabled.
1662 #[must_use]
1663 pub fn to_device(self, device: &Device) -> Self {
1664 let target_device = device.clone().with_autodiff_context_from(&self.device());
1665 Self::new(K::to_device(self.primitive, &target_device))
1666 }
1667
1668 /// Select tensor elements along the given dimension corresponding to the given indices.
1669 ///
1670 /// # Arguments
1671 ///
1672 /// * `dim` - The dimension to select from. Supports negative indexing.
1673 /// * `indices` - The indices of the elements to select.
1674 ///
1675 /// # Example
1676 ///
1677 /// ```rust
1678 /// use burn_tensor::{Tensor, Int};
1679 ///
1680 /// let device = Default::default();
1681 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [4.0, 5.0, 6.0]], &device);
1682 /// let indices = Tensor::<1, Int>::from_data([0], &device);
1683 /// let tensor = tensor.select(0, indices);
1684 /// println!("{tensor}");
1685 /// // [[1.0, -2.0, 3.0]]
1686 /// ```
1687 pub fn select(self, dim: impl AsIndex, indices: Tensor<1, Int>) -> Self {
1688 let dim = unwrap_dim_index(dim.try_dim_index(D), "Select");
1689 Self::new(K::select(self.primitive, dim, indices.primitive))
1690 }
1691
1692 /// Assign the selected elements along the given dimension corresponding to the given indices
1693 /// from the value tensor to the original tensor using the requested update operation.
1694 ///
1695 /// # Note
1696 /// - `IndexingUpdateOp::Add` accumulates values at the selected positions (`+=`). For
1697 /// booleans, `Add` is logical or.
1698 /// - `IndexingUpdateOp::Assign` replaces values at the selected positions (`=`), when
1699 /// supported by the backend.
1700 /// - `IndexingUpdateOp::Mul` multiplies values at the selected positions (`*=`), when
1701 /// supported by the backend.
1702 ///
1703 /// When `indices` contains duplicate entries, behavior varies by operation:
1704 /// - For `Add`, accumulation is supported, though results may be non-deterministic on GPU
1705 /// backends.
1706 /// - For `Assign`, duplicate indices result in undefined behavior for both the forward result
1707 /// and the backward gradients.
1708 /// - For `Mul`, duplicate indices are multiplied in an unspecified order and backward
1709 /// gradients are undefined.
1710 /// - For `Min` and `Max`, duplicate indices are reduced to the minimum/maximum over all
1711 /// contributions, but the backward gradients are undefined.
1712 ///
1713 /// For deterministic results and correct gradient calculation across all operations,
1714 /// `indices` should contain unique entries.
1715 ///
1716 /// # Arguments
1717 ///
1718 /// * `dim` - The dimension along which to select. Supports negative indexing.
1719 /// * `indices` - The indices to select from the tensor.
1720 /// * `values` - The values to assign to the selected indices.
1721 /// * `update` - The operation used to update the existing values at the indexed positions.
1722 ///
1723 /// # Example
1724 ///
1725 /// Example using a 3D tensor:
1726 ///
1727 /// With `IndexingUpdateOp::Add`:
1728 ///
1729 /// `input[indices[i], j, k] += values[i, j, k]; // dim = 0`
1730 /// `input[i, indices[j], k] += values[i, j, k]; // dim = 1`
1731 /// `input[i, j, indices[k]] += values[i, j, k]; // dim = 2`
1732 ///
1733 /// With `IndexingUpdateOp::Assign`, when supported by the backend, the same indexed locations
1734 /// are replaced instead:
1735 ///
1736 /// `input[indices[i], j, k] = values[i, j, k]; // dim = 0`
1737 /// `input[i, indices[j], k] = values[i, j, k]; // dim = 1`
1738 /// `input[i, j, indices[k]] = values[i, j, k]; // dim = 2`
1739 ///
1740 /// With `IndexingUpdateOp::Mul`, when supported by the backend, the selected values are
1741 /// multiplied instead:
1742 ///
1743 /// `input[indices[i], j, k] *= values[i, j, k]; // dim = 0`
1744 /// `input[i, indices[j], k] *= values[i, j, k]; // dim = 1`
1745 /// `input[i, j, indices[k]] *= values[i, j, k]; // dim = 2`
1746 ///
1747 /// # Warning
1748 ///
1749 /// Not all backends have runtime bound checks for the indices, so make sure they are valid.
1750 /// Otherwise, out of bounds indices could lead to unexpected results instead of panicking.
1751 ///
1752 /// # Panics
1753 /// If the backend doesn't support the requested update operation.
1754 pub fn select_assign(
1755 self,
1756 dim: impl AsIndex,
1757 indices: Tensor<1, Int>,
1758 values: Tensor<D, K>,
1759 update: IndexingUpdateOp,
1760 ) -> Self {
1761 let dim = unwrap_dim_index(dim.try_dim_index(D), "Select Assign");
1762 check!(TensorCheck::select_assign::<D>(
1763 dim,
1764 &indices.shape(),
1765 &values.shape()
1766 ));
1767
1768 Self::new(K::select_assign(
1769 self.primitive,
1770 dim,
1771 indices.primitive,
1772 values.primitive,
1773 update,
1774 ))
1775 }
1776
1777 /// Update the given tensor with the value tensor where the mask is true.
1778 ///
1779 /// This is similar to [mask_fill](Tensor::mask_fill), however the value is a tensor instead of
1780 /// a scalar.
1781 ///
1782 /// # Example
1783 ///
1784 /// ```rust
1785 /// use burn_tensor::{Tensor, Shape, Bool};
1786 ///
1787 /// let device = Default::default();
1788 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
1789 /// let mask = Tensor::<2, Bool>::from_data([[true, false, true], [false, true, false]], &device);
1790 /// let value = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
1791 /// let tensor = tensor.mask_where(mask, value);
1792 /// println!("{tensor}");
1793 /// // [[2.0, -2.0, 4.0], [5.0, 2.0, 6.0]]
1794 /// ```
1795 pub fn mask_where(self, mask: Tensor<D, Bool>, value: Self) -> Self {
1796 Self::new(K::mask_where(
1797 self.primitive,
1798 mask.primitive,
1799 value.primitive,
1800 ))
1801 }
1802
1803 /// Update the given tensor with the value where the mask is true.
1804 ///
1805 /// This is similar to [mask_where](Tensor::mask_where), however the value is a scalar instead of
1806 /// a tensor.
1807 ///
1808 /// # Example
1809 ///
1810 /// ```rust
1811 /// use burn_tensor::{Tensor, Shape, Bool};
1812 ///
1813 /// let device = Default::default();
1814 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
1815 /// let mask = Tensor::<2, Bool>::from_data([[true, false, true], [false, true, false]], &device);
1816 /// let tensor = tensor.mask_fill(mask, 3.0);
1817 /// println!("{tensor}");
1818 /// // [[3.0, -2.0, 3.0], [5.0, 3.0, 6.0]]
1819 /// ```
1820 pub fn mask_fill<E: ElementConversion>(self, mask: Tensor<D, Bool>, value: E) -> Self {
1821 let value = Scalar::new(value, &self.dtype());
1822 Self::new(K::mask_fill(self.primitive, mask.primitive, value))
1823 }
1824
1825 /// Selects the elements of the tensor where `mask` is `true`, returned as a 1D tensor in the
1826 /// order of the flattened input tensor.
1827 ///
1828 /// The mask must have the same shape as the tensor. Unlike `torch.masked_select`, the mask is
1829 /// not broadcast against the tensor.
1830 ///
1831 /// # Notes
1832 ///
1833 /// The number of selected elements is data-dependent, so this synchronizes with the device,
1834 /// consistent with [`argwhere`](Tensor::argwhere) and [`nonzero`](Tensor::nonzero). On backends
1835 /// without a native implementation, this reads the entire mask back to the host and computes
1836 /// the indices on the CPU; on lazy backends, it also forces the execution of pending
1837 /// operations.
1838 ///
1839 /// This makes each call a synchronization point between the host and the device: prefer
1840 /// calling it once on final results (e.g. filtering predictions) rather than inside
1841 /// performance-critical loops.
1842 ///
1843 /// On an autodiff backend, gradients flow back to the selected elements; positions where
1844 /// `mask` is `false` receive a zero gradient.
1845 ///
1846 /// # Panics
1847 ///
1848 /// - If `mask` does not have the same shape as the tensor.
1849 /// - If the mask data cannot be read synchronously (e.g. on wasm); use
1850 /// [`mask_select_async`](Tensor::mask_select_async) instead.
1851 ///
1852 /// # Example
1853 ///
1854 /// ```rust
1855 /// use burn_tensor::{Tensor, Bool};
1856 ///
1857 /// let device = Default::default();
1858 /// let tensor = Tensor::<2>::from_data([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], &device);
1859 /// let mask = Tensor::<2, Bool>::from_data([[true, false, true], [false, true, false]], &device);
1860 /// let selected = tensor.mask_select(mask);
1861 /// println!("{selected}");
1862 /// // [1.0, 3.0, 5.0]
1863 /// ```
1864 pub fn mask_select(self, mask: Tensor<D, Bool>) -> Tensor<1, K> {
1865 crate::try_read_sync(self.mask_select_async(mask)).expect(
1866 "Failed to read tensor data synchronously. Try using mask_select_async instead.",
1867 )
1868 }
1869
1870 /// Selects the elements of the tensor where `mask` is `true`, returned as a 1D tensor in the
1871 /// order of the flattened input tensor.
1872 ///
1873 /// Asynchronous version of [`mask_select`](Tensor::mask_select), for backends where the
1874 /// mask cannot be read synchronously (e.g. wasm). The synchronization cost remains; only the
1875 /// waiting is non-blocking.
1876 ///
1877 /// # Panics
1878 ///
1879 /// If `mask` does not have the same shape as the tensor.
1880 pub async fn mask_select_async(self, mask: Tensor<D, Bool>) -> Tensor<1, K> {
1881 check!(TensorCheck::mask_select(&self.shape(), &mask.shape()));
1882 Tensor::new(K::mask_select(self.primitive, mask.primitive).await)
1883 }
1884
1885 /// Gather tensor elements corresponding to the given indices from the specified dim.
1886 /// The dimension supports negative indexing.
1887 ///
1888 /// Example using a 3D tensor:
1889 ///
1890 /// `output[i, j, k] = input[indices[i, j, k], j, k]; // dim = 0`
1891 /// `output[i, j, k] = input[i, indices[i, j, k], k]; // dim = 1`
1892 /// `output[i, j, k] = input[i, j, indices[i, j, k]]; // dim = 2`
1893 ///
1894 /// # Notes
1895 ///
1896 /// The index tensor should have the same shape as the original tensor except for the dim
1897 /// specified.
1898 ///
1899 /// # Warning
1900 /// Not all backends have runtime bound checks for the indices, so make sure the they are valid.
1901 /// Otherwise, out of bounds indices could lead to unexpected results instead of panicking.
1902 pub fn gather(self, dim: impl AsIndex, indices: Tensor<D, Int>) -> Self {
1903 let dim = unwrap_dim_index(dim.try_dim_index(D), "Gather");
1904 check!(TensorCheck::gather::<D>(
1905 dim,
1906 &self.shape(),
1907 &indices.shape()
1908 ));
1909
1910 Self::new(K::gather(dim, self.primitive, indices.primitive))
1911 }
1912
1913 /// Assign the gathered elements corresponding to the given indices along the specified dimension
1914 /// from the value tensor to the original tensor using the requested update operation.
1915 ///
1916 /// Example using a 3D tensor:
1917 ///
1918 /// With `IndexingUpdateOp::Add`:
1919 ///
1920 /// `input[indices[i, j, k], j, k] += values[i, j, k]; // dim = 0`
1921 /// `input[i, indices[i, j, k], k] += values[i, j, k]; // dim = 1`
1922 /// `input[i, j, indices[i, j, k]] += values[i, j, k]; // dim = 2`
1923 ///
1924 /// With `IndexingUpdateOp::Assign`, when supported by the backend, the same indexed locations
1925 /// are replaced instead:
1926 ///
1927 /// `input[indices[i, j, k], j, k] = values[i, j, k]; // dim = 0`
1928 /// `input[i, indices[i, j, k], k] = values[i, j, k]; // dim = 1`
1929 /// `input[i, j, indices[i, j, k]] = values[i, j, k]; // dim = 2`
1930 ///
1931 /// With `IndexingUpdateOp::Mul`, when supported by the backend, the indexed values are
1932 /// multiplied instead:
1933 ///
1934 /// `input[indices[i, j, k], j, k] *= values[i, j, k]; // dim = 0`
1935 /// `input[i, indices[i, j, k], k] *= values[i, j, k]; // dim = 1`
1936 /// `input[i, j, indices[i, j, k]] *= values[i, j, k]; // dim = 2`
1937 ///
1938 /// # Arguments
1939 /// * `dim` - The axis along which to scatter elements. Supports negative indexing.
1940 /// * `indices` - The indices of the elements to scatter.
1941 /// * `values` - The values to scatter into the tensor.
1942 /// * `update` - The operation used to update the existing values at the indexed positions.
1943 ///
1944 /// # Notes
1945 ///
1946 /// The index tensor should have the same shape as the original tensor except for the specified
1947 /// dimension. The value and index tensors should have the same shape.
1948 ///
1949 /// When `indices` contains duplicate entries, behavior varies by operation:
1950 /// - For `Add`, accumulation is supported, though results may be non-deterministic on GPU
1951 /// backends.
1952 /// - For `Assign`, duplicate indices result in undefined behavior for both the forward result
1953 /// and the backward gradients.
1954 /// - For `Mul`, duplicate indices are multiplied in an unspecified order and backward
1955 /// gradients are undefined.
1956 /// - For `Min` and `Max`, duplicate indices are reduced to the minimum/maximum over all
1957 /// contributions, but the backward gradients are undefined.
1958 ///
1959 /// For deterministic results and correct gradient calculation across all operations,
1960 /// `indices` should contain unique entries.
1961 ///
1962 /// Other references to the input tensor will not be modified by this operation.
1963 ///
1964 /// # Warning
1965 /// Not all backends have runtime bound checks for the indices, so make sure the they are valid.
1966 /// Otherwise, out of bounds indices could lead to unexpected results instead of panicking.
1967 ///
1968 /// # Panics
1969 /// If the backend doesn't support the requested update operation.
1970 pub fn scatter(
1971 self,
1972 dim: impl AsIndex,
1973 indices: Tensor<D, Int>,
1974 values: Self,
1975 update: IndexingUpdateOp,
1976 ) -> Self {
1977 let dim = unwrap_dim_index(dim.try_dim_index(D), "Scatter");
1978 check!(TensorCheck::scatter::<D>(
1979 dim,
1980 &self.shape(),
1981 &indices.shape(),
1982 &values.shape()
1983 ));
1984
1985 Self::new(K::scatter(
1986 dim,
1987 self.primitive,
1988 indices.primitive,
1989 values.primitive,
1990 update,
1991 ))
1992 }
1993
1994 /// Multi-dimensional scatter: update the tensor at locations given by `indices` using the specified `update` operation.
1995 ///
1996 /// The size of `indices`'s last axis (call it `K`) indexes the leading `K` dims of `self`;
1997 /// the batch shape `indices.shape[0..M-1]` is preserved. `values` has shape
1998 /// `indices.shape[0..M-1] ++ self.shape[K..D]`. Constraints: `K <= D` and `M >= 1`.
1999 ///
2000 /// # Arguments
2001 /// * `indices` - The indices of the elements to scatter.
2002 /// * `values` - The values to scatter into the tensor.
2003 /// * `update` - The operation used to update the existing values at the indexed positions (e.g., add).
2004 ///
2005 /// # Note
2006 ///
2007 /// When `indices` contains duplicate entries, behavior varies by operation:
2008 /// - For `Add`, accumulation is supported, though results may be non-deterministic on GPU
2009 /// backends.
2010 /// - For other operations (`Assign`, `Mul`, `Min`, `Max`), duplicate indices result in
2011 /// undefined behavior for both the forward result and the backward gradients.
2012 ///
2013 /// For deterministic results and correct gradient calculation across all operations,
2014 /// `indices` should contain unique entries.
2015 ///
2016 /// # Warning
2017 ///
2018 /// Not all backends have runtime bound checks for the indices, so make sure they are valid.
2019 /// Otherwise, out of bounds indices could lead to unexpected results instead of panicking.
2020 pub fn scatter_nd<const M: usize, const DV: usize>(
2021 self,
2022 indices: Tensor<M, Int>,
2023 values: Tensor<DV, K>,
2024 update: IndexingUpdateOp,
2025 ) -> Self {
2026 check!(TensorCheck::scatter_nd::<D, M, DV>(
2027 &self.shape(),
2028 &indices.shape(),
2029 &values.shape()
2030 ));
2031 Self::new(K::scatter_nd(
2032 self.primitive,
2033 indices.primitive,
2034 values.primitive,
2035 update,
2036 ))
2037 }
2038
2039 /// Multi-dimensional gather: collect slices from `self` at multi-index locations
2040 /// specified by `indices`.
2041 ///
2042 /// The size of `indices`'s last axis (call it `K`) indexes the leading `K` dims of `self`;
2043 /// the batch shape `indices.shape[0..M-1]` is preserved. The output has shape
2044 /// `indices.shape[0..M-1] ++ self.shape[K..D]`. Constraints: `K <= D` and `M >= 1`.
2045 ///
2046 /// # Warning
2047 ///
2048 /// Not all backends have runtime bound checks for the indices, so make sure they are valid.
2049 /// Otherwise, out of bounds indices could lead to unexpected results instead of panicking.
2050 pub fn gather_nd<const M: usize, const DV: usize>(
2051 self,
2052 indices: Tensor<M, Int>,
2053 ) -> Tensor<DV, K> {
2054 check!(TensorCheck::gather_nd::<D, M, DV>(&indices.shape()));
2055 Tensor::new(K::gather_nd(self.primitive, indices.primitive))
2056 }
2057
2058 /// Converts the data of the current tensor.
2059 ///
2060 /// # Note
2061 ///
2062 /// For better performance, prefer using a [Transaction](crate::Transaction) when reading multiple
2063 /// tensors at once. This may improve laziness, especially if executed on a different
2064 /// thread in native environments.
2065 ///
2066 /// # Returns
2067 ///
2068 /// The tensor data.
2069 ///
2070 /// # Panics
2071 ///
2072 /// Panics if the backend fails to read the tensor data or the platform doesn't support
2073 /// synchronous readback.
2074 #[track_caller]
2075 pub fn into_data(self) -> TensorData {
2076 self.try_into_data().expect(
2077 "Error while reading data: use `try_into_data` instead to catch the error at runtime",
2078 )
2079 }
2080
2081 /// Converts the data of the current tensor and returns any error that might have occurred since the
2082 /// last time the device was synchronized.
2083 ///
2084 /// # Note
2085 ///
2086 /// For better performance, prefer using a [Transaction](crate::Transaction) when reading multiple
2087 /// tensors at once. This may improve laziness, especially if executed on a different
2088 /// thread in native environments.
2089 ///
2090 /// # Errors
2091 ///
2092 /// Returns an error if the backend fails to read the tensor data.
2093 ///
2094 /// # Panics
2095 ///
2096 /// Panics if the platform doesn't support synchronous readback.
2097 pub fn try_into_data(self) -> Result<TensorData, ExecutionError> {
2098 try_into_data_sync_impl(self.primitive, K::KIND)
2099 }
2100
2101 /// Converts the data of the current tensor.
2102 ///
2103 /// # Note
2104 ///
2105 /// For better performance, prefer using a [Transaction](crate::Transaction) when reading multiple
2106 /// tensors at once. This may improve laziness, especially if executed on a different
2107 /// thread in native environments.
2108 ///
2109 /// # Returns
2110 ///
2111 /// The tensor data.
2112 ///
2113 /// # Panics
2114 ///
2115 /// Panics if the backend fails to read the tensor data or the platform doesn't support
2116 /// synchronous readback.
2117 #[track_caller]
2118 pub fn to_data(&self) -> TensorData {
2119 self.try_to_data().expect(
2120 "Error while reading data: use `try_to_data` instead to catch the error at runtime",
2121 )
2122 }
2123
2124 /// Converts the data of the current tensor.
2125 ///
2126 /// # Note
2127 ///
2128 /// For better performance, prefer using a [Transaction](crate::Transaction) when reading multiple
2129 /// tensors at once. This may improve laziness, especially if executed on a different
2130 /// thread in native environments.
2131 ///
2132 /// # Errors
2133 ///
2134 /// Returns an error if the backend fails to read the tensor data.
2135 ///
2136 /// # Panics
2137 ///
2138 /// Panics if the platform doesn't support synchronous readback.
2139 pub fn try_to_data(&self) -> Result<TensorData, ExecutionError> {
2140 self.clone().try_into_data()
2141 }
2142
2143 /// Returns the data of the current tensor.
2144 pub fn into_data_async(
2145 self,
2146 ) -> impl Future<Output = Result<TensorData, ExecutionError>> + Send {
2147 into_data_async_impl(self.primitive, K::KIND)
2148 }
2149
2150 /// Returns the data of the current tensor.
2151 pub fn to_data_async(&self) -> impl Future<Output = Result<TensorData, ExecutionError>> + Send {
2152 into_data_async_impl(self.primitive.clone(), K::KIND)
2153 }
2154
2155 /// Copies the tensor data to host memory and converts it to the dtype represented by `E`.
2156 ///
2157 /// The conversion is a no-op if the dtype is the same as the current dtype.
2158 ///
2159 /// See: [`Tensor::try_to_data_as`].
2160 ///
2161 /// # Returns
2162 /// A `TensorData` with dtype `E::dtype()`.
2163 ///
2164 /// # Panics
2165 ///
2166 /// Panics if synchronous readback isn't supported, tensor execution or storage access fails,
2167 /// or the data can't be converted to `E`.
2168 #[track_caller]
2169 pub fn to_data_as<E: Element>(&self) -> TensorData {
2170 self.try_to_data_as::<E>()
2171 .unwrap_or_else(|err| panic!("Failed to read tensor data: {err}"))
2172 }
2173
2174 /// Copies the tensor data to host memory and converts it to the dtype represented by `E`.
2175 ///
2176 /// By contract, this will yield the same result as
2177 /// `tensor.try_to_data()?.try_cast_as::<E>()`.
2178 ///
2179 /// The conversion is a no-op if the dtype is the same as the current dtype.
2180 ///
2181 /// # Errors
2182 ///
2183 /// Returns an error if tensor execution or storage access fails, or the data can't be
2184 /// converted to `E`.
2185 ///
2186 /// # Panics
2187 ///
2188 /// Panics if the platform doesn't support synchronous readback.
2189 pub fn try_to_data_as<E: Element>(&self) -> Result<TensorData, TensorReadError> {
2190 Ok(self.try_to_data()?.try_cast_as::<E>()?)
2191 }
2192
2193 /// Copies the tensor data to a host [`Vec<E>`], converting the dtype when necessary.
2194 ///
2195 /// By contract, this will yield the same result as
2196 /// `tensor.try_to_data_as::<E>()?.try_to_vec::<E>()`.
2197 ///
2198 /// # Errors
2199 ///
2200 /// Returns an error if tensor execution or storage access fails, or the data can't be
2201 /// converted to `E`.
2202 ///
2203 /// # Panics
2204 ///
2205 /// Panics if the platform doesn't support synchronous readback.
2206 pub fn try_to_vec_as<E: Element>(&self) -> Result<Vec<E>, TensorReadError> {
2207 Ok(self.try_to_data_as::<E>()?.try_to_vec()?)
2208 }
2209
2210 /// Copies the tensor data to host memory and converts it to `dtype`.
2211 ///
2212 /// The conversion is a no-op if the dtype is the same as the current dtype.
2213 ///
2214 /// See: [`Tensor::try_to_data_dtype`].
2215 ///
2216 /// # Returns
2217 /// A `TensorData` with the requested `dtype`.
2218 ///
2219 /// # Panics
2220 ///
2221 /// Panics if synchronous readback isn't supported, tensor execution or storage access fails,
2222 /// or the data can't be converted to `dtype`.
2223 #[track_caller]
2224 pub fn to_data_dtype(&self, dtype: DType) -> TensorData {
2225 self.try_to_data_dtype(dtype)
2226 .unwrap_or_else(|err| panic!("Failed to read tensor data as {dtype:?}: {err}"))
2227 }
2228
2229 /// Copies the tensor data to host memory and converts it to `dtype`.
2230 ///
2231 /// By contract, this will yield the same result as
2232 /// `tensor.try_to_data()?.try_cast(dtype)`.
2233 ///
2234 /// The conversion is a no-op if the dtype is the same as the current dtype.
2235 ///
2236 /// # Errors
2237 ///
2238 /// Returns an error if tensor execution or storage access fails, or the data can't be
2239 /// converted to `dtype`.
2240 ///
2241 /// # Panics
2242 ///
2243 /// Panics if the platform doesn't support synchronous readback.
2244 pub fn try_to_data_dtype(&self, dtype: DType) -> Result<TensorData, TensorReadError> {
2245 Ok(self.try_to_data()?.try_cast(dtype)?)
2246 }
2247
2248 /// Reads the tensor data into host memory and converts it to the dtype represented by `E`.
2249 ///
2250 /// The conversion is a no-op if the dtype is the same as the current dtype.
2251 ///
2252 /// See: [`Tensor::try_into_data_as`].
2253 ///
2254 /// # Returns
2255 /// A `TensorData` with dtype `E::dtype()`.
2256 ///
2257 /// # Panics
2258 ///
2259 /// Panics if synchronous readback isn't supported, tensor execution or storage access fails,
2260 /// or the data can't be converted to `E`.
2261 #[track_caller]
2262 pub fn into_data_as<E: Element>(self) -> TensorData {
2263 self.try_into_data_as::<E>()
2264 .unwrap_or_else(|err| panic!("Failed to read tensor data: {err}"))
2265 }
2266
2267 /// Reads the tensor data into host memory and converts it to the dtype represented by `E`.
2268 ///
2269 /// By contract, this will yield the same result as
2270 /// `tensor.try_into_data()?.try_cast_as::<E>()`.
2271 ///
2272 /// The conversion is a no-op if the dtype is the same as the current dtype.
2273 ///
2274 /// # Errors
2275 ///
2276 /// Returns an error if tensor execution or storage access fails, or the data can't be
2277 /// converted to `E`.
2278 ///
2279 /// # Panics
2280 ///
2281 /// Panics if the platform doesn't support synchronous readback.
2282 pub fn try_into_data_as<E: Element>(self) -> Result<TensorData, TensorReadError> {
2283 Ok(self.try_into_data()?.try_cast_as::<E>()?)
2284 }
2285
2286 /// Reads the tensor data into a host [`Vec<E>`], converting the dtype when necessary.
2287 ///
2288 /// By contract, this will yield the same result as
2289 /// `tensor.try_into_data_as::<E>()?.try_into_vec::<E>()`.
2290 ///
2291 /// # Errors
2292 ///
2293 /// Returns an error if tensor execution or storage access fails, or the data can't be
2294 /// converted to `E`.
2295 ///
2296 /// # Panics
2297 ///
2298 /// Panics if the platform doesn't support synchronous readback.
2299 pub fn try_into_vec_as<E: Element>(self) -> Result<Vec<E>, TensorReadError> {
2300 Ok(self.try_into_data_as::<E>()?.try_into_vec::<E>()?)
2301 }
2302
2303 /// Reads the tensor data into host memory and converts it to `dtype`.
2304 ///
2305 /// The conversion is a no-op if the dtype is the same as the current dtype.
2306 ///
2307 /// See: [`Tensor::try_into_data_dtype`].
2308 ///
2309 /// # Returns
2310 /// A `TensorData` with the requested `dtype`.
2311 ///
2312 /// # Panics
2313 ///
2314 /// Panics if synchronous readback isn't supported, tensor execution or storage access fails,
2315 /// or the data can't be converted to `dtype`.
2316 #[track_caller]
2317 pub fn into_data_dtype(self, dtype: DType) -> TensorData {
2318 self.try_into_data_dtype(dtype)
2319 .unwrap_or_else(|err| panic!("Failed to read tensor data as {dtype:?}: {err}"))
2320 }
2321
2322 /// Reads the tensor data into host memory and converts it to `dtype`.
2323 ///
2324 /// By contract, this will yield the same result as
2325 /// `tensor.try_into_data()?.try_cast(dtype)`.
2326 ///
2327 /// The conversion is a no-op if the dtype is the same as the current dtype.
2328 ///
2329 /// # Errors
2330 ///
2331 /// Returns an error if tensor execution or storage access fails, or the data can't be
2332 /// converted to `dtype`.
2333 ///
2334 /// # Panics
2335 ///
2336 /// Panics if the platform doesn't support synchronous readback.
2337 pub fn try_into_data_dtype(self, dtype: DType) -> Result<TensorData, TensorReadError> {
2338 Ok(self.try_into_data()?.try_cast(dtype)?)
2339 }
2340
2341 /// Create a tensor from the given data on the given device.
2342 pub fn from_data<T>(data: T, options: impl Into<TensorCreationOptions>) -> Self
2343 where
2344 T: Into<TensorData>,
2345 {
2346 let data = data.into();
2347 check!(TensorCheck::creation_ops::<D>(
2348 "From Data",
2349 data.shape().as_slice()
2350 ));
2351
2352 // Use the given dtype when provided, otherwise default device dtype
2353 let opt = options.into();
2354 let dtype = opt.resolve_dtype::<K>();
2355
2356 Self::new(K::from_data(data, &opt.device, dtype))
2357 }
2358
2359 /// Repeat the tensor along the given dimension.
2360 ///
2361 /// The output tensor has the same shape, except along the given dimension.
2362 ///
2363 /// # Arguments
2364 /// - `dim`: The dimension to repeat. Supports negative indexing.
2365 /// - `times`: The number of times to repeat the tensor along the given dimension in the new tensor.
2366 ///
2367 /// # Returns
2368 ///
2369 /// A new tensor with the given dimension repeated `times` times.
2370 ///
2371 /// # Example
2372 ///
2373 /// ```rust
2374 /// use burn_tensor::Tensor;
2375 ///
2376 /// let device = Default::default();
2377 /// // Create a 2D tensor with dimensions [3, 2]
2378 /// let tensor = Tensor::<2>::from_data([[3.0, 4.9], [2.0, 1.9], [4.0, 5.9]], &device);
2379 ///
2380 /// // Repeat the tensor along the dimension 0 twice.
2381 /// // [[3.0, 4.9], [2.0, 1.9], [4.0, 5.9], [3.0, 4.9], [2.0, 1.9], [4.0, 5.9]]
2382 /// // The resulting tensor will have dimensions [6, 2].
2383 /// let repeated = tensor.repeat_dim(0, 2);
2384 /// println!("{repeated}");
2385 /// ```
2386 pub fn repeat_dim(self, dim: impl AsIndex, times: usize) -> Self {
2387 let dim = unwrap_dim_index(dim.try_dim_index(D), "Repeat");
2388 if times > 0 {
2389 Self::new(K::repeat_dim(self.primitive, dim, times))
2390 } else {
2391 let shape = self.shape().repeat(dim, times).unwrap();
2392 Self::new(K::empty(shape, &self.device(), self.creation_dtype()))
2393 }
2394 }
2395
2396 /// Repeat the tensor along the given dimensions.
2397 /// # Arguments
2398 /// - `sizes`: Borrowed slice of the number of times to repeat each dimension.
2399 ///
2400 /// # Returns
2401 ///
2402 /// A new tensor with the given dimensions repeated `times` times.
2403 ///
2404 /// # Panics
2405 ///
2406 /// If `sizes` contains more elements than the number of dimensions.
2407 ///
2408 /// # Example
2409 ///
2410 /// ```rust
2411 ///
2412 /// use burn_tensor::Tensor;
2413 ///
2414 /// let device = Default::default();
2415 /// // Create a 2D tensor with dimensions [3, 2]
2416 /// let tensor = Tensor::<2>::from_data([[3.0, 4.9], [2.0, 1.9], [4.0, 5.9]], &device);
2417 ///
2418 /// // Repeat the tensor along the dimension 0 twice and the dimension 0 once.
2419 /// // [[3.0, 4.9], [2.0, 1.9], [4.0, 5.9], [3.0, 4.9], [2.0, 1.9], [4.0, 5.9]]
2420 /// // The resulting tensor will have dimensions [6, 2].
2421 /// let repeated = tensor.repeat(&[2, 1]);
2422 /// ```
2423 pub fn repeat(self, sizes: &[usize]) -> Self {
2424 if sizes.contains(&0) {
2425 let mut shape = self.shape();
2426 for (dim, ×) in sizes.iter().enumerate() {
2427 shape = shape.repeat(dim, times).unwrap();
2428 }
2429
2430 return Self::new(K::empty(shape, &self.device(), self.creation_dtype()));
2431 }
2432
2433 let mut tensor = self;
2434 for (dim, ×) in sizes.iter().enumerate() {
2435 if times > 1 {
2436 tensor = tensor.repeat_dim(dim, times);
2437 }
2438 }
2439 tensor
2440 }
2441
2442 /// Applies element-wise equal comparison.
2443 ///
2444 /// # Returns
2445 /// A boolean tensor that is `true` where input is equal to `other` and `false` elsewhere.
2446 ///
2447 /// # Panics
2448 ///
2449 /// If the two tensors don't have the same shape.
2450 ///
2451 /// # Example
2452 ///
2453 /// ```rust
2454 /// use burn_tensor::Tensor;
2455 ///
2456 /// let device = Default::default();
2457 /// let t1 = Tensor::<2>::from_data([[2.0, 4.9], [2.0, 1.9], [4.0, 5.9]], &device);
2458 /// let t2 = Tensor::<2>::from_data([[3.0, 4.9], [2.0, 1.9], [4.0, 5.9]], &device);
2459 /// // Compare the elements of the two 2D tensors with dimensions [3, 2].
2460 /// // [[false, true], [true, true], [true, true]]
2461 /// let equal = t1.equal(t2);
2462 /// println!("{equal}");
2463 /// ```
2464 pub fn equal(self, other: Self) -> Tensor<D, Bool> {
2465 check!(TensorCheck::binary_ops_ew("Equal", &self, &other));
2466 Tensor::new(K::equal(self.primitive, other.primitive))
2467 }
2468
2469 /// Applies element-wise non-equality comparison.
2470 ///
2471 /// # Returns
2472 /// A boolean tensor that is `true` where input is not equal to `other` and `false` elsewhere.
2473 ///
2474 /// # Panics
2475 ///
2476 /// If the two tensors don't have the same shape.
2477 ///
2478 /// # Example
2479 ///
2480 /// ```rust
2481 /// use burn_tensor::Tensor;
2482 ///
2483 /// let device = Default::default();
2484 /// let t1 = Tensor::<2>::from_data([[2.0, 4.9], [2.0, 1.9], [4.0, 5.9]], &device);
2485 /// let t2 = Tensor::<2>::from_data([[3.0, 4.9], [2.0, 1.9], [4.0, 5.9]], &device);
2486 /// // Compare the elements of the two 2D tensors for inequality.
2487 /// // [[true, false], [false, false], [false, false]]
2488 /// let not_equal = t1.not_equal(t2);
2489 /// println!("{not_equal}");
2490 /// ```
2491 pub fn not_equal(self, other: Self) -> Tensor<D, Bool> {
2492 check!(TensorCheck::binary_ops_ew("NotEqual", &self, &other));
2493 Tensor::new(K::not_equal(self.primitive, other.primitive))
2494 }
2495
2496 /// Applies element wise equal comparison and returns a boolean tensor.
2497 ///
2498 /// # Arguments
2499 ///
2500 /// * `other` - The scalar to compare.
2501 ///
2502 /// # Example
2503 ///
2504 /// ```rust
2505 /// use burn_tensor::{Tensor, Shape};
2506 ///
2507 /// let device = Default::default();
2508 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
2509 /// let tensor = tensor.equal_scalar(3.0);
2510 /// println!("{tensor}");
2511 /// // [[false, false, true], [false, false, false]]
2512 /// ```
2513 pub fn equal_scalar<E: Element>(self, other: E) -> Tensor<D, Bool> {
2514 let other = Scalar::new(other, &self.dtype());
2515 Tensor::new(K::equal_scalar(self.primitive, other))
2516 }
2517
2518 /// Applies element wise non-equality comparison and returns a boolean tensor.
2519 ///
2520 /// # Arguments
2521 ///
2522 /// * `other` - The scalar to compare.
2523 ///
2524 /// # Example
2525 ///
2526 /// ```rust
2527 /// use burn_tensor::{Tensor, Shape};
2528 ///
2529 /// let device = Default::default();
2530 /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
2531 /// let tensor = tensor.not_equal_scalar(3.0);
2532 /// println!("{tensor}");
2533 /// // [[true, true, false], [true, true, true]]
2534 /// ```
2535 pub fn not_equal_scalar<E: Element>(self, other: E) -> Tensor<D, Bool> {
2536 let other = Scalar::new(other, &self.dtype());
2537 Tensor::new(K::not_equal_scalar(self.primitive, other))
2538 }
2539
2540 /// Alias for [equal_scalar](Self::equal_scalar).
2541 pub fn equal_elem<E: Element>(self, other: E) -> Tensor<D, Bool> {
2542 self.equal_scalar(other)
2543 }
2544
2545 /// Alias for [not_equal_scalar](Self::not_equal_scalar).
2546 pub fn not_equal_elem<E: Element>(self, other: E) -> Tensor<D, Bool> {
2547 self.not_equal_scalar(other)
2548 }
2549
2550 /// Concatenates all tensors into a new one along the given dimension.
2551 /// The dimension supports negative indexing.
2552 ///
2553 /// # Panics
2554 ///
2555 /// - If `dim` is higher than the rank.
2556 /// - If `tensors` is an empty vector.
2557 /// - If all tensors don't have the same shape (the dimension `dim` is ignored).
2558 ///
2559 /// # Example
2560 ///
2561 /// ```rust
2562 /// use burn_tensor::Tensor;
2563 ///
2564 /// let device = Default::default();
2565 /// let t1 = Tensor::<2>::from_data([[3.0, 4.9, 2.0, 1.0], [2.0, 1.9, 3.0, 1.0]], &device);
2566 /// let t2 = Tensor::<2>::from_data([[4.0, 5.9, 8.0], [1.4, 5.8, 6.0]], &device);
2567 ///
2568 /// // Concatenate the two tensors with shapes [2, 4] and [2, 3] along the dimension 1.
2569 /// // [[3.0, 4.9, 2.0, 1.0, 4.0, 5.9, 8.0], [2.0, 1.9, 3.0, 1.0, 1.4, 5.8, 6.0]]
2570 /// // The resulting tensor will have shape [2, 7].
2571 /// let concat = Tensor::cat(vec![t1, t2], 1);
2572 /// println!("{concat}");
2573 /// ```
2574 pub fn cat(tensors: Vec<Self>, dim: impl AsIndex) -> Self {
2575 let dim = unwrap_dim_index(dim.try_dim_index(D), "Cat");
2576 check!(TensorCheck::cat(tensors.as_slice(), dim));
2577
2578 // Filter out tensors with size 0 along the concatenation dimension.
2579 // Empty tensors don't contribute to the output and would cause issues
2580 // in backend implementations (e.g., division by zero in slice_assign).
2581 // Safety: TensorCheck::cat ensures tensors is non-empty
2582 let first_tensor = tensors.first().unwrap();
2583 let device = first_tensor.device();
2584 let dtype = first_tensor.creation_dtype();
2585 let mut shape = first_tensor.shape();
2586
2587 let non_empty_primitives: Vec<_> = tensors
2588 .into_iter()
2589 .filter(|t| t.shape()[dim] > 0)
2590 .map(|t| t.primitive)
2591 .collect();
2592
2593 // If all tensors were empty, return an empty tensor with size 0 on concat dim
2594 if non_empty_primitives.is_empty() {
2595 shape[dim] = 0;
2596 return Self::new(K::empty(shape, &device, dtype));
2597 }
2598
2599 Self::new(K::cat(non_empty_primitives, dim))
2600 }
2601
2602 /// Concatenates all tensors into a new one along a new dimension.
2603 /// The dimension supports negative indexing.
2604 ///
2605 /// # Panics
2606 ///
2607 /// - If all tensors don't have the same shape.
2608 /// - If the given dimension is outside the `D + 1` valid insertion positions.
2609 ///
2610 /// # Example
2611 ///
2612 /// ```rust
2613 /// use burn_tensor::Tensor;
2614 ///
2615 /// let device = Default::default();
2616 /// let t1 = Tensor::<2>::from_data([[3.0, 4.9, 2.0], [2.0, 1.9, 3.0]], &device);
2617 /// let t2 = Tensor::<2>::from_data([[4.0, 5.9, 8.0], [1.4, 5.8, 6.0]], &device);
2618 /// let t3 = Tensor::<2>::from_data([[4.0, 5.9, 8.0], [1.4, 5.8, 6.0]], &device);
2619 ///
2620 /// // Concatenate the three tensors with shape [2, 3] along a new dimension, 0.
2621 /// // [[[3.0, 4.9, 2.0], [2.0, 1.9, 3.0]],
2622 /// // [[4.0, 5.9, 8.0], [1.4, 5.8, 6.0]],
2623 /// // [[4.0, 5.9, 8.0], [1.4, 5.8, 6.0]]]
2624 /// // The resulting tensor will have shape [3, 2, 3].
2625 /// let stacked= Tensor::stack::<3>(vec![t1, t2, t3], 0);
2626 /// println!("{stacked}");
2627 /// ```
2628 pub fn stack<const D2: usize>(tensors: Vec<Tensor<D, K>>, dim: impl AsIndex) -> Tensor<D2, K> {
2629 let dim = unwrap_dim_index(dim.try_dim_index(D + 1), "Stack");
2630 check!(TensorCheck::stack::<D, K, D2>(tensors.as_slice(), dim));
2631 let tensors = tensors.into_iter().map(|t| t.unsqueeze_dim(dim)).collect();
2632 Tensor::<D2, K>::cat(tensors, dim)
2633 }
2634
2635 /// Iterate over slices of tensors alongside a given dimension.
2636 /// The dimension supports negative indexing.
2637 ///
2638 /// # Panics
2639 ///
2640 /// If given dimension is greater than or equal to tensor rank.
2641 ///
2642 /// # Returns
2643 ///
2644 /// A tensor iterator.
2645 ///
2646 /// # Example
2647 ///
2648 /// ```rust
2649 /// use burn_tensor::Tensor;
2650 /// let device = Default::default();
2651 /// let tensor = Tensor::<2>::from_data([[3.0, 4.9, 2.0], [2.0, 1.9, 3.0]], &device);
2652 /// // Given a 2D tensor with dimensions [2, 3], iterate over slices of tensors along the dimension 0.
2653 /// let iter = tensor.iter_dim(0);
2654 /// for (i,tensor) in iter.enumerate() {
2655 /// println!("Tensor {}: {}", i, tensor);
2656 /// // Tensor 0: Tensor { data: [[3.0, 4.9, 2.0]], ... }
2657 /// // Tensor 1: Tensor { data: [[2.0, 1.9, 3.0]], ... }
2658 /// }
2659 /// ```
2660 pub fn iter_dim(self, dim: impl AsIndex) -> DimIter<D, K> {
2661 let dim = unwrap_dim_index(dim.try_dim_index(D), "Iter Dim");
2662 DimIter::new(self, dim)
2663 }
2664
2665 /// Returns a new tensor with the given dimension narrowed to the given range.
2666 /// The dimension supports negative indexing.
2667 ///
2668 /// # Panics
2669 ///
2670 /// - If the dimension is greater than the number of dimensions of the tensor.
2671 /// - If the given range exceeds the number of elements on the given dimension.
2672 ///
2673 /// # Returns
2674 ///
2675 /// A new tensor with the given dimension narrowed to the given range.
2676 ///
2677 /// # Example
2678 ///
2679 /// ```rust
2680 /// use burn_tensor::Tensor;
2681 ///
2682 /// let device = Default::default();
2683 /// // Create a 2D tensor with dimensions [4, 3]
2684 /// let tensor = Tensor::<2>::from_data(
2685 /// [
2686 /// [3.0, 4.9, 2.0],
2687 /// [2.0, 1.9, 3.0],
2688 /// [6.0, 1.5, 7.0],
2689 /// [3.0, 4.9, 9.0],
2690 /// ],
2691 /// &device,
2692 /// );
2693 /// // Narrow the tensor along the dimension 0, keeping 3 elements starting from index 1.
2694 /// // [[2.0, 1.9, 3.0], [6.0, 1.5, 7.0], [3.0, 4.9, 9.0]]
2695 /// // The resulting tensor will have dimensions [3, 3].
2696 /// let narrowed = tensor.narrow(0, 1, 3);
2697 /// println!("{narrowed}");
2698 /// ```
2699 pub fn narrow(self, dim: impl AsIndex, start: usize, length: usize) -> Self {
2700 let dim = unwrap_dim_index(dim.try_dim_index(D), "Narrow");
2701 check!(TensorCheck::narrow(&self, dim, start, length));
2702 let dims = self.dims();
2703
2704 let ranges: [Range<usize>; D] = dims
2705 .iter()
2706 .enumerate()
2707 .map(|(i, d)| {
2708 if i == dim {
2709 start..(start + length)
2710 } else {
2711 0..*d
2712 }
2713 })
2714 .collect::<Vec<_>>()
2715 .try_into()
2716 .unwrap();
2717
2718 Self::slice(self, ranges)
2719 }
2720
2721 /// Attempts to split the tensor into a specified number of chunks along a given dimension.
2722 /// The dimension supports negative indexing.
2723 /// May return less chunks than requested if the tensor size is not divisible by the number of chunks.
2724 ///
2725 /// When the given dimension is evenly divisible by the number of chunks, the chunks will be of equal size.
2726 /// Otherwise all chunks will be of equal size except for the last one.
2727 ///
2728 /// # Panics
2729 ///
2730 /// If the dimension is greater than the number of dimensions of the tensor.
2731 ///
2732 /// # Returns
2733 /// A vector of tensors.
2734 ///
2735 /// # Example
2736 ///
2737 /// ```rust
2738 /// use burn_tensor::Tensor;
2739 ///
2740 /// let device = Default::default();
2741 /// // Create a 2D tensor with dimensions [4, 3]
2742 /// let tensor = Tensor::<2>::from_data(
2743 /// [
2744 /// [3.0, 4.9, 2.0],
2745 /// [2.0, 1.9, 3.0],
2746 /// [6.0, 1.5, 7.0],
2747 /// [3.0, 4.9, 9.0],
2748 /// ],
2749 /// &device,
2750 /// );
2751 /// // Split the tensor along the dimension 1 into 2 chunks.
2752 /// // The first chuck will have shape [4, 2]:
2753 /// // [[3.0, 4.9], [2.0, 1.9], [6.0, 1.5], [3.0, 4.9]]
2754 /// // The second chunk will have shape [4, 1]:
2755 /// // [[2.0], [3.0], [7.0], [9.0]]
2756 /// let chunks = tensor.chunk(2, 1);
2757 /// println!("{chunks:?}");
2758 /// ```
2759 pub fn chunk(self, chunks: usize, dim: impl AsIndex) -> Vec<Self> {
2760 let dim = unwrap_dim_index(dim.try_dim_index(D), "Chunk");
2761 let size = self.shape()[dim];
2762 if size < chunks {
2763 return (0..size)
2764 .map(|i| Self::narrow(self.clone(), dim, i, 1))
2765 .collect();
2766 }
2767
2768 let mut tensors = Vec::with_capacity(chunks);
2769 let mut sum_chunk_size = 0;
2770 if size.is_multiple_of(chunks) {
2771 let chunk_size = size / chunks;
2772 for _ in 0..chunks {
2773 tensors.push(Self::narrow(self.clone(), dim, sum_chunk_size, chunk_size));
2774 sum_chunk_size += chunk_size;
2775 }
2776 } else {
2777 let chunk_size = (size / chunks) + 1; // assumes not divisible
2778 for _ in 0..chunks - 1 {
2779 tensors.push(Self::narrow(self.clone(), dim, sum_chunk_size, chunk_size));
2780 sum_chunk_size += chunk_size;
2781 }
2782 let remainder = size % chunk_size;
2783 tensors.push(Self::narrow(self.clone(), dim, sum_chunk_size, remainder));
2784 }
2785
2786 tensors
2787 }
2788
2789 /// Splits the tensor into chunks of a specified size along a given dimension.
2790 /// The dimension supports negative indexing.
2791 /// Each chunk is a view of the original tensor.
2792 ///
2793 /// If the tensor size along the given dimension is not divisible by `split_size`,
2794 /// then the last chunk will be smaller.
2795 ///
2796 /// # Panics
2797 ///
2798 /// If the specified dimension to split along is greater than the number of dimensions of the tensor.
2799 ///
2800 /// # Returns
2801 ///
2802 /// A vector of tensors.
2803 ///
2804 /// # Example
2805 /// ```rust
2806 /// use burn_tensor::Tensor;
2807 ///
2808 /// let device = Default::default();
2809 /// // Create a 1D tensor with 5 elements
2810 /// let tensor = Tensor::<1>::from_data([0.0, 1.0, 2.0, 3.0, 4.0], &device);
2811 /// // Split the tensor into chunks of size 2 along dimension 0
2812 /// let chunks = tensor.split(2, 0);
2813 /// // The result is a vector of tensors:
2814 /// // [Tensor([0.0, 1.0]), Tensor([2.0, 3.0]), Tensor([4.0])]
2815 /// println!("{:?}", chunks);
2816 /// ```
2817 pub fn split(self, split_size: usize, dim: impl AsIndex) -> Vec<Self> {
2818 let dim = unwrap_dim_index(dim.try_dim_index(D), "Split");
2819 check!(TensorCheck::split::<D>(&self.shape(), split_size, dim));
2820 let size = self.shape()[dim];
2821 let mut tensors = Vec::new();
2822
2823 let mut start = 0;
2824 while start < size {
2825 let length = usize::min(split_size, size - start);
2826 tensors.push(Self::narrow(self.clone(), dim, start, length));
2827 start += length;
2828 }
2829
2830 tensors
2831 }
2832
2833 /// Splits the tensor into chunks with the specified sizes along a given dimension.
2834 /// The dimension supports negative indexing.
2835 /// Each chunk is a view of the original tensor.
2836 ///
2837 /// The sizes of the chunks are specified in the `split_sizes` vector. The sum of the sizes
2838 /// in `split_sizes` must equal the size of the tensor along the specified dimension.
2839 ///
2840 /// # Panics
2841 ///
2842 /// If the specified dimension to split along is greater than the number of dimensions of the tensor or
2843 /// if the sum of `dim_sizes` does not equal the size of the tensor along `dim`.
2844 ///
2845 /// # Returns
2846 ///
2847 /// A vector of tensors.
2848 ///
2849 /// # Example
2850 /// ```rust
2851 /// use burn_tensor::Tensor;
2852 ///
2853 /// let device = Default::default();
2854 /// // Create a 1D tensor with 5 elements
2855 /// let tensor = Tensor::<1>::from_data([0.0, 1.0, 2.0, 3.0, 4.0], &device);
2856 /// // Split the tensor into chunks with sizes [2, 3] along dimension 0
2857 /// let chunks = tensor.split_with_sizes(vec![2, 3], 0);
2858 /// // The result is a vector of tensors:
2859 /// // [Tensor([0.0, 1.0]), Tensor([2.0, 3.0, 4.0])]
2860 /// println!("{:?}", chunks);
2861 /// ```
2862 pub fn split_with_sizes(self, split_sizes: Vec<usize>, dim: impl AsIndex) -> Vec<Self> {
2863 let dim = unwrap_dim_index(dim.try_dim_index(D), "Split With Sizes");
2864 check!(TensorCheck::split_with_sizes::<D>(
2865 &self.shape(),
2866 &split_sizes,
2867 dim
2868 ));
2869 let mut tensors = Vec::new();
2870
2871 let mut start = 0;
2872 for length in split_sizes {
2873 if length == 0 {
2874 continue;
2875 }
2876 tensors.push(Self::narrow(self.clone(), dim, start, length));
2877 start += length;
2878 }
2879
2880 tensors
2881 }
2882
2883 /// Tests if any element in the `tensor` evaluates to True.
2884 ///
2885 /// # Arguments
2886 ///
2887 /// * `tensor` - The tensor to test. All input tensor types (Float, Int, Bool) are supported.
2888 ///
2889 /// # Returns
2890 ///
2891 /// A boolean tensor `Tensor<1, Bool>` containing a single element, True if any element in the input tensor
2892 /// evaluates to True, False otherwise.
2893 ///
2894 /// # Example
2895 ///
2896 /// ```rust
2897 /// use burn_tensor::{Tensor, Bool};
2898 ///
2899 /// let device = Default::default();
2900 /// let tensor = Tensor::<2, Bool>::from_data([[true,false,true],[false,true,false]], &device);
2901 /// let tensor_two = Tensor::<2, Bool>::from_data([[false,false,false],[false,false,false]], &device);
2902 ///
2903 /// // Given a 2D tensor with dimensions [2, 3], test if any element in the tensor evaluates to True.
2904 /// let any_tensor = tensor.any();
2905 /// println!("{}", any_tensor);
2906 /// // Tensor { data: [true], ... }
2907 ///
2908 /// // Given a 2D tensor with dimensions [2, 3], test if any element in the tensor evaluates to True.
2909 /// let any_tensor_two = tensor_two.any();
2910 /// println!("{}", any_tensor_two);
2911 /// // Tensor { data: [false], ... }
2912 /// ```
2913 pub fn any(self) -> Tensor<1, Bool> {
2914 Tensor::new(K::any(self.primitive))
2915 }
2916
2917 /// Tests if any element in the `tensor` evaluates to True along a given dimension `dim`.
2918 ///
2919 /// # Arguments
2920 ///
2921 /// * `tensor` - The tensor to test. All input tensor types (Float, Int, Bool) are supported.
2922 /// * `dim` - The axis along which to test. Supports negative indexing.
2923 ///
2924 /// # Returns
2925 ///
2926 /// A boolean tensor `Tensor<D, Bool>` with the same shape as input `tensor`, except in the `dim` axis
2927 /// where the size is 1. The elem in the `dim` axis is True if any element along this dim in the input
2928 /// evaluates to True, False otherwise.
2929 ///
2930 /// # Example
2931 ///
2932 /// ```rust
2933 /// use burn_tensor::{Tensor, Bool};
2934 ///
2935 /// let device = Default::default();
2936 /// let tensor =
2937 /// Tensor::<2, Bool>::from_data([[true, false, false], [false, true, false]], &device);
2938 /// // Check if any element in the tensor evaluates to True along the dimension 1.
2939 /// // [[true], [true]],
2940 /// let any_dim = tensor.clone().any_dim(1);
2941 /// println!("{any_dim}");
2942 /// ```
2943 pub fn any_dim(self, dim: impl AsIndex) -> Tensor<D, Bool> {
2944 let dim = unwrap_dim_index(dim.try_dim_index(D), "Any");
2945 Tensor::new(K::any_dim(self.primitive, dim))
2946 }
2947
2948 /// Tests if all elements in the `tensor` evaluate to True.
2949 ///
2950 /// # Arguments
2951 ///
2952 /// * `tensor` - The tensor to test. All input tensor types (Float, Int, Bool) are supported.
2953 ///
2954 /// # Returns
2955 ///
2956 /// A boolean tensor `Tensor<1, Bool>` with a single element, True if all elements in the input tensor
2957 /// evaluate to True, False otherwise.
2958 ///
2959 /// # Example
2960 ///
2961 /// ```rust
2962 /// use burn_tensor::{Tensor, Bool};
2963 ///
2964 /// let device = Default::default();
2965 /// let tensor =
2966 /// Tensor::<2, Bool>::from_data([[true, false, true], [true, true, true]], &device);
2967 /// // Check if all elements in the tensor evaluate to True (which is not the case).
2968 /// // [false]
2969 /// let all = tensor.all();
2970 /// println!("{all}");
2971 /// ```
2972 pub fn all(self) -> Tensor<1, Bool> {
2973 Tensor::new(K::all(self.primitive))
2974 }
2975
2976 /// Tests if all elements in the `tensor` evaluate to True along a given dimension `dim`.
2977 ///
2978 /// # Arguments
2979 ///
2980 /// * `tensor` - The tensor to test. All input tensor types (Float, Int, Bool) are supported.
2981 /// * `dim` - The axis along which to test. Supports negative indexing.
2982 ///
2983 /// # Returns
2984 ///
2985 /// A boolean tensor `Tensor<D, Bool>` with the same shape as input `tensor`, except in the `dim` axis
2986 /// where the size is 1. The elem in the `dim` axis is True if all elements along this dim in the input
2987 /// evaluates to True, False otherwise.
2988 ///
2989 /// # Example
2990 ///
2991 /// ```rust
2992 /// use burn_tensor::{Tensor, Bool};
2993 ///
2994 /// let device = Default::default();
2995 /// let tensor =
2996 /// Tensor::<2, Bool>::from_data([[true, true, false], [true, true, true]], &device);
2997 /// // Check if all elements in the tensor evaluate to True along the dimension 1.
2998 /// // [[true, true, false]]
2999 /// let all_dim = tensor.clone().all_dim(0);
3000 /// println!("{all_dim}");
3001 /// ```
3002 pub fn all_dim(self, dim: impl AsIndex) -> Tensor<D, Bool> {
3003 let dim = unwrap_dim_index(dim.try_dim_index(D), "All");
3004 Tensor::new(K::all_dim(self.primitive, dim))
3005 }
3006
3007 /// Convert the tensor into a scalar.
3008 ///
3009 /// # Panics
3010 ///
3011 /// - If the tensor doesn't have exactly one element.
3012 /// - If synchronous readback isn't supported or the backend fails to read the tensor data.
3013 /// - If the data can't be converted to `E`.
3014 ///
3015 /// # Returns
3016 ///
3017 /// The scalar value of the tensor.
3018 ///
3019 /// # Example
3020 ///
3021 /// ```rust
3022 /// use burn_tensor::Tensor;
3023 ///
3024 /// let device = Default::default();
3025 /// let tensor = Tensor::<2>::from_data([[3.0]], &device);
3026 /// // Convert the tensor with a single element into a scalar.
3027 /// let scalar: f32 = tensor.into_scalar();
3028 /// println!("{scalar}");
3029 /// ```
3030 #[track_caller]
3031 pub fn into_scalar<E: Element>(self) -> E {
3032 self.try_into_scalar::<E>().expect(
3033 "Error while reading data: use `try_into_scalar` instead to catch the error at runtime",
3034 )
3035 }
3036
3037 /// Converts the tensor into a scalar and returns any error that occurred since the
3038 /// last time the device was synchronized.
3039 ///
3040 /// # Errors
3041 ///
3042 /// Returns an error if the tensor doesn't contain exactly one element, the backend fails to
3043 /// read its data, or the data can't be converted to `E`.
3044 ///
3045 /// # Panics
3046 ///
3047 /// Panics if the platform doesn't support synchronous readback.
3048 ///
3049 /// # Returns
3050 ///
3051 /// The scalar value of the tensor.
3052 pub fn try_into_scalar<E: Element>(self) -> Result<E, TensorReadError> {
3053 let data = self.try_into_data()?;
3054 Self::_unpack_scalar::<E>(data)
3055 }
3056
3057 /// Convert the tensor into a scalar asynchronously.
3058 ///
3059 /// # Panics
3060 ///
3061 /// Panics if the tensor doesn't contain exactly one element or its data can't be converted
3062 /// to `E`.
3063 ///
3064 /// # Errors
3065 ///
3066 /// Returns an error if the backend fails to read the tensor data.
3067 pub async fn into_scalar_async<E: Element>(self) -> Result<E, ExecutionError> {
3068 check!(TensorCheck::into_scalar::<D>(&self.shape()));
3069 let data = self.into_data_async().await?;
3070 Ok(Self::_unpack_scalar::<E>(data)
3071 .unwrap_or_else(|err| panic!("Failed to convert tensor data to a scalar: {err}")))
3072 }
3073
3074 /// Try to convert the tensor into a scalar asynchronously.
3075 ///
3076 /// # Errors
3077 ///
3078 /// Returns an error if the tensor doesn't contain exactly one element, the backend fails to
3079 /// read its data, or the data can't be converted to `E`.
3080 pub async fn try_into_scalar_async<E: Element>(self) -> Result<E, TensorReadError> {
3081 let data = self.into_data_async().await?;
3082 Self::_unpack_scalar::<E>(data)
3083 }
3084
3085 fn _unpack_scalar<E: Element>(data: TensorData) -> Result<E, TensorReadError> {
3086 let actual = data.num_elements();
3087 if actual != 1 {
3088 return Err(TensorReadError::InvalidShape {
3089 expected: 1,
3090 actual,
3091 });
3092 }
3093
3094 let mut values = data.try_into_vec_as::<E>()?;
3095 let actual = values.len();
3096 if actual != 1 {
3097 return Err(TensorReadError::InvalidShape {
3098 expected: 1,
3099 actual,
3100 });
3101 }
3102
3103 Ok(values.pop().expect("scalar element count was validated"))
3104 }
3105
3106 /// Broadcast the tensor to the given shape.
3107 ///
3108 /// Only singleton dimensions can be expanded to a larger size. Other dimensions must have the same size
3109 /// (which can be inferred with `-1`).
3110 ///
3111 /// # Arguments
3112 ///
3113 /// * `shape` - The shape to broadcast the tensor to.
3114 /// Can contain -1 for dimensions that should be inferred.
3115 /// The number of elements in the shape must be greater or equal as
3116 /// the number of dimensions of the tensor.
3117 ///
3118 /// # Panics
3119 ///
3120 /// If the tensor cannot be broadcasted to the given shape.
3121 ///
3122 /// # Returns
3123 ///
3124 /// A new tensor with the given shape.
3125 ///
3126 /// # Example
3127 ///
3128 /// ```rust
3129 /// use burn_tensor::Tensor;
3130 ///
3131 /// let device = Default::default();
3132 /// // Create a 2D tensor with dimensions [3, 1]
3133 /// let tensor = Tensor::<2>::from_data([[1.], [2.], [3.]], &device);
3134 /// // Expand the tensor to a new shape [3, 4]
3135 /// // [[1.0, 1.0, 1.0, 1.0], [2.0, 2.0, 2.0, 2.0], [3.0, 3.0, 3.0, 3.0]]
3136 /// let expanded = tensor.expand([3, 4]);
3137 /// println!("{}", expanded);
3138 /// ```
3139 pub fn expand<const D2: usize, S: BroadcastArgs<D, D2>>(self, shape: S) -> Tensor<D2, K> {
3140 let shape = shape.into_shape(&self.shape());
3141 check!(TensorCheck::expand::<D, D2>(
3142 "Expand",
3143 &self.shape(),
3144 &shape,
3145 ));
3146
3147 Tensor::<D2, K>::new(K::expand(self.primitive, shape))
3148 }
3149
3150 /// Unfold windows along a dimension.
3151 ///
3152 /// Returns a view of the tensor with all complete windows of size `size` in dimension `dim`;
3153 /// where windows are advanced by `step` at each index.
3154 ///
3155 /// The number of windows is `0` when `shape[dim] < size`, and otherwise
3156 /// `(shape[dim] - size) / step + 1`.
3157 ///
3158 /// The new view will have the unfolded dimension replaced by two dimensions;
3159 /// one in the position of the original dimension, with size equal to the number of windows,
3160 /// and one appended to the right-most position, with size equal to `size`.
3161 ///
3162 /// # Warning
3163 ///
3164 /// For the `ndarray` backend; this is not a view but a copy
3165 /// with duplicated data.
3166 ///
3167 /// # Arguments
3168 ///
3169 /// * `dim` - the dimension to unfold.
3170 /// * `size` - the size of each unfolded window.
3171 /// * `step` - the step between each window.
3172 ///
3173 /// # Returns
3174 ///
3175 /// A tensor view with the shape ``[pre=..., windows, post=..., size]``.
3176 pub fn unfold<const D2: usize, I: AsIndex>(
3177 self,
3178 dim: I,
3179 size: usize,
3180 step: usize,
3181 ) -> Tensor<D2, K> {
3182 let dim = unwrap_dim_index(dim.try_dim_index(D), "Unfold");
3183 check!(TensorCheck::unfold::<D, D2>(
3184 "Unfold",
3185 &self.shape(),
3186 dim,
3187 size,
3188 step,
3189 ));
3190 Tensor::<D2, K>::new(K::unfold(self.primitive, dim, size, step))
3191 }
3192}
3193
3194/// Iterator given by (Tensor::iter_dim).
3195pub struct DimIter<const D: usize, K>
3196where
3197 K: Basic,
3198{
3199 start: usize,
3200 end: usize,
3201 dim: usize,
3202 ranges: [Range<usize>; D],
3203 tensor: Tensor<D, K>,
3204}
3205
3206impl<const D: usize, K: Basic> Iterator for DimIter<D, K> {
3207 type Item = Tensor<D, K>;
3208
3209 fn next(&mut self) -> Option<Self::Item> {
3210 if self.start >= self.end {
3211 return None;
3212 }
3213
3214 let mut ranges = self.ranges.clone();
3215 ranges[self.dim] = self.start..(self.start + 1);
3216
3217 let slice = self.tensor.clone().slice(ranges);
3218 self.start += 1;
3219
3220 Some(slice)
3221 }
3222}
3223
3224impl<const D: usize, K: Basic> ExactSizeIterator for DimIter<D, K> {
3225 fn len(&self) -> usize {
3226 self.end - self.start
3227 }
3228}
3229
3230impl<const D: usize, K: Basic> DoubleEndedIterator for DimIter<D, K> {
3231 fn next_back(&mut self) -> Option<Self::Item> {
3232 if self.start >= self.end {
3233 return None;
3234 }
3235
3236 let mut ranges = self.ranges.clone();
3237 ranges[self.dim] = (self.end - 1)..self.end;
3238
3239 let slice = self.tensor.clone().slice(ranges);
3240 self.end = self.end.saturating_sub(1);
3241
3242 Some(slice)
3243 }
3244}
3245
3246impl<const D: usize, K: Basic> DimIter<D, K> {
3247 fn new(tensor: Tensor<D, K>, dim: usize) -> Self {
3248 let dims = tensor.dims();
3249 let ranges = dims
3250 .iter()
3251 .map(|&dim| 0..dim)
3252 .collect::<Vec<Range<usize>>>();
3253 let ranges: [Range<usize>; D] = ranges.try_into().unwrap();
3254 Self {
3255 end: dims[dim],
3256 ranges,
3257 start: 0,
3258 dim,
3259 tensor,
3260 }
3261 }
3262}
3263
3264struct DataIterFmt {
3265 data: TensorData,
3266 precision: Option<usize>,
3267}
3268
3269fn fmt_float<E: Element>(elem: E, precision: Option<usize>) -> String {
3270 match precision {
3271 Some(p) => format!("{elem:.p$}"),
3272 None => fmt_elem(elem),
3273 }
3274}
3275
3276fn fmt_elem<E: Element>(elem: E) -> String {
3277 format!("{elem:?}")
3278}
3279
3280// TODO: refactor display
3281impl DataIterFmt {
3282 fn next(&self) -> String {
3283 match self.data.dtype() {
3284 DType::F64 => fmt_float(self.next_elem::<f64>(), self.precision),
3285 DType::F32 | DType::Flex32 => fmt_float(self.next_elem::<f32>(), self.precision),
3286 DType::F16 => fmt_float(self.next_elem::<burn_std::f16>(), self.precision),
3287 DType::BF16 => fmt_float(self.next_elem::<burn_std::bf16>(), self.precision),
3288 DType::I64 => fmt_elem(self.next_elem::<i64>()),
3289 DType::I32 => fmt_elem(self.next_elem::<i32>()),
3290 DType::I16 => fmt_elem(self.next_elem::<i16>()),
3291 DType::I8 => fmt_elem(self.next_elem::<i8>()),
3292 DType::U64 => fmt_elem(self.next_elem::<u64>()),
3293 DType::U32 => fmt_elem(self.next_elem::<u32>()),
3294 DType::U16 => fmt_elem(self.next_elem::<u16>()),
3295 DType::U8 => fmt_elem(self.next_elem::<u8>()),
3296 DType::Bool(store) => match store {
3297 burn_std::BoolStore::Native => fmt_elem(self.next_elem::<bool>()),
3298 burn_std::BoolStore::U8 => fmt_elem(self.next_elem::<u8>().to_bool()),
3299 burn_std::BoolStore::U32 => fmt_elem(self.next_elem::<u32>().to_bool()),
3300 },
3301 // Never panic: quantized tensors are displayed as metadata only.
3302 DType::QFloat(_) => String::from("<quantized>"),
3303 }
3304 }
3305
3306 fn next_elem<E: Element>(&self) -> E {
3307 self.data.iter::<E>().next().unwrap()
3308 }
3309}
3310
3311// The Display-formatting recursion used to live as generic methods on
3312// `Tensor<D, K>` here. It has been outlined to non-generic free functions
3313// (`display_fmt_*`, `slice_bridge_by_kind`, `push_newline_indent_impl`) below,
3314// so it is compiled exactly once inside `burn-tensor` instead of being
3315// re-monomorphized for every `(D, K)` in downstream crates. That outlining is
3316// the difference between a ~7s and a ~0.5s incremental release rebuild for a
3317// program that just calls `println!("{tensor}")`.
3318
3319#[derive(Clone, Debug)]
3320/// Options for Tensor pretty printing
3321pub struct PrintOptions {
3322 /// number of elements to start summarizing tensor
3323 pub threshold: usize,
3324
3325 /// number of starting elements and ending elements to display
3326 pub edge_items: usize,
3327
3328 /// Precision for floating point numbers
3329 pub precision: Option<usize>,
3330}
3331
3332static PRINT_OPTS: RwLock<PrintOptions> = RwLock::new(PrintOptions::const_default());
3333
3334impl PrintOptions {
3335 /// Print options with default values
3336 pub const fn const_default() -> Self {
3337 Self {
3338 threshold: 1000,
3339 edge_items: 3,
3340 precision: None,
3341 }
3342 }
3343}
3344
3345impl Default for PrintOptions {
3346 fn default() -> Self {
3347 Self::const_default()
3348 }
3349}
3350
3351/// Set print options
3352pub fn set_print_options(options: PrintOptions) {
3353 let mut print_opts = PRINT_OPTS.write();
3354 *print_opts = options;
3355}
3356
3357/// Pretty print tensors
3358impl<const D: usize, K> core::fmt::Display for Tensor<D, K>
3359where
3360 K: Basic,
3361{
3362 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3363 display_fmt_impl(&self.primitive, K::KIND, K::name(), f)
3364 }
3365}
3366
3367/// Trait used for movedim arguments
3368pub trait MovedimArgs {
3369 /// Converts into a set of dimensions `Vec<usize>` for the `tensor.movedim()` function
3370 fn into_dim_vec<const D: usize>(self) -> Vec<usize>;
3371}
3372
3373impl<I: AsIndex> MovedimArgs for Vec<I> {
3374 fn into_dim_vec<const D: usize>(self) -> Vec<usize> {
3375 let set = self
3376 .into_iter()
3377 .map(|dim| unwrap_dim_index(dim.try_dim_index(D), "Movedim"))
3378 .collect::<Vec<usize>>();
3379 check!(TensorCheck::movedim_args_vec::<D>(&set));
3380
3381 set
3382 }
3383}
3384
3385macro_rules! impl_movedim_args {
3386 ($($ty:ty),*) => {
3387 $(
3388 impl MovedimArgs for $ty {
3389 fn into_dim_vec<const D: usize>(self) -> Vec<usize> {
3390 vec![unwrap_dim_index(self.try_dim_index(D), "Movedim")]
3391 }
3392 }
3393 )*
3394 };
3395}
3396
3397impl_movedim_args!(usize, isize, i64, u64, i32, u32, i16, u16, i8, u8);
3398
3399/// Trait used for reshape arguments.
3400pub trait ReshapeArgs<const D2: usize>: Debug {
3401 /// Converts to a shape.
3402 fn into_shape<const D: usize>(self, source: Shape) -> Shape;
3403}
3404
3405impl<const D2: usize, I: AsIndex> ReshapeArgs<D2> for [I; D2] {
3406 fn into_shape<const D: usize>(self, source: Shape) -> Shape {
3407 unwrap_shape_reshape(source.reshape(self))
3408 }
3409}
3410
3411impl<const D2: usize> ReshapeArgs<D2> for Shape {
3412 fn into_shape<const D: usize>(self, source: Shape) -> Shape {
3413 unwrap_shape_reshape(source.reshape(self))
3414 }
3415}
3416
3417/// Trait used for broadcast arguments.
3418pub trait BroadcastArgs<const D1: usize, const D2: usize> {
3419 /// Converts to a shape.
3420 fn into_shape(self, shape: &Shape) -> Shape;
3421}
3422
3423impl<const D1: usize, const D2: usize> BroadcastArgs<D1, D2> for Shape {
3424 fn into_shape(self, _shape: &Shape) -> Shape {
3425 self
3426 }
3427}
3428
3429impl<const D1: usize, const D2: usize, E: AsIndex> BroadcastArgs<D1, D2> for [E; D2] {
3430 // Passing -1 as the size for a dimension means not changing the size of that dimension.
3431 fn into_shape(self, shape: &Shape) -> Shape {
3432 if self.len() < shape.num_dims() {
3433 panic!(
3434 "Broadcast arguments must be greater than the number of dimensions! got {}, need at least {}",
3435 self.len(),
3436 shape.num_dims()
3437 );
3438 }
3439
3440 // Zip the two shapes in reverse order and replace -1 with the actual dimension value.
3441 let new_shape: Vec<_> = self
3442 .iter()
3443 .rev()
3444 .map(|x| {
3445 let primitive = x.as_index();
3446 if primitive < -1 || primitive == 0 {
3447 panic!(
3448 "Broadcast arguments must be positive or -1! Got {}",
3449 primitive
3450 );
3451 }
3452 primitive
3453 })
3454 .zip(shape.iter().rev().chain(repeat(&0)).take(self.len())) // Pad the original shape with 0s
3455 .map(|(x, &y)| if x == -1 { y } else { x as usize })
3456 .collect::<Vec<_>>()
3457 .into_iter()
3458 .rev()
3459 .collect();
3460
3461 if new_shape.contains(&0) {
3462 panic!(
3463 "Cannot substitute -1 for a non-existing dimension! Got {:?}",
3464 new_shape
3465 );
3466 }
3467
3468 let new_shape: [usize; D2] = new_shape.try_into().unwrap();
3469
3470 Shape::from(new_shape)
3471 }
3472}
3473
3474impl<const D: usize, K> Serialize for Tensor<D, K>
3475where
3476 K: Basic,
3477{
3478 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3479 let data = self.to_data();
3480 data.serialize(serializer)
3481 }
3482}
3483
3484impl<'de, const D: usize, K> Deserialize<'de> for Tensor<D, K>
3485where
3486 K: Basic,
3487{
3488 fn deserialize<De: Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
3489 let tensor = Tensor::from_data(TensorData::deserialize(deserializer)?, &Device::default());
3490 Ok(tensor)
3491 }
3492}
3493
3494/// Non-generic outline of `into_data_async`. The public method just calls this
3495/// helper, so its monomorphization (per `D`/`K`) is trivial — the heavy async
3496/// state-machine code lives here, compiled once inside `burn-tensor`.
3497async fn into_data_async_impl(
3498 primitive: BridgeTensor,
3499 kind: Kind,
3500) -> Result<TensorData, ExecutionError> {
3501 match kind {
3502 Kind::Float => <Float as BasicOps>::into_data_async(primitive).await,
3503 Kind::Int => <Int as BasicOps>::into_data_async(primitive).await,
3504 Kind::Bool => <Bool as BasicOps>::into_data_async(primitive).await,
3505 }
3506}
3507
3508fn slice_bridge_by_kind(p: BridgeTensor, slices: &[Slice], kind: Kind) -> BridgeTensor {
3509 match kind {
3510 Kind::Float => <Float as BasicOps>::slice(p, slices),
3511 Kind::Int => <Int as BasicOps>::slice(p, slices),
3512 Kind::Bool => <Bool as BasicOps>::slice(p, slices),
3513 }
3514}
3515
3516#[allow(clippy::too_many_arguments)]
3517fn display_fmt_inner(
3518 primitive: &BridgeTensor,
3519 kind: Kind,
3520 acc: &mut String,
3521 depth: usize,
3522 multi_index: &mut [usize],
3523 range: (usize, usize),
3524 precision: Option<usize>,
3525 dims: &[usize],
3526) {
3527 let (start, end) = range;
3528 let rank = dims.len();
3529 for i in start..end {
3530 if i > 0 {
3531 acc.push_str(", ");
3532 }
3533 multi_index[depth] = i;
3534 let slices: Vec<Slice> = (0..rank)
3535 .map(|d| Slice::from((multi_index[d] as i64)..((multi_index[d] + 1) as i64)))
3536 .collect();
3537 let sliced = slice_bridge_by_kind(primitive.clone(), &slices, kind);
3538 let data = burn_std::reader::try_read_sync(into_data_async_impl(sliced, kind));
3539 if let Some(Ok(data)) = data {
3540 let elem = DataIterFmt { data, precision }.next();
3541 acc.push_str(&elem);
3542 } else {
3543 acc.push_str("<Tensor data not available>");
3544 }
3545 }
3546}
3547
3548fn push_newline_indent_impl(acc: &mut String, indent: usize) {
3549 acc.push('\n');
3550 for _ in 0..indent {
3551 acc.push(' ');
3552 }
3553}
3554
3555#[allow(clippy::too_many_arguments)]
3556fn display_fmt_outer(
3557 primitive: &BridgeTensor,
3558 kind: Kind,
3559 acc: &mut String,
3560 depth: usize,
3561 multi_index: &mut [usize],
3562 print_options: &PrintOptions,
3563 summarize: bool,
3564 range: (usize, usize),
3565 dims: &[usize],
3566) {
3567 let (start, end) = range;
3568 for i in start..end {
3569 if i > start {
3570 acc.push(',');
3571 push_newline_indent_impl(acc, depth + 1);
3572 }
3573 acc.push('[');
3574 multi_index[depth] = i;
3575 display_fmt_recursive(
3576 primitive,
3577 kind,
3578 acc,
3579 depth + 1,
3580 multi_index,
3581 print_options,
3582 summarize,
3583 dims,
3584 );
3585 acc.push(']');
3586 }
3587}
3588
3589#[allow(clippy::too_many_arguments)]
3590fn display_fmt_recursive(
3591 primitive: &BridgeTensor,
3592 kind: Kind,
3593 acc: &mut String,
3594 depth: usize,
3595 multi_index: &mut [usize],
3596 print_options: &PrintOptions,
3597 summarize: bool,
3598 dims: &[usize],
3599) {
3600 let edge_items = print_options.edge_items;
3601
3602 if depth == 0 {
3603 acc.push('[');
3604 }
3605
3606 if depth == dims.len() - 1 {
3607 if summarize && dims[depth] > 2 * edge_items {
3608 display_fmt_inner(
3609 primitive,
3610 kind,
3611 acc,
3612 depth,
3613 multi_index,
3614 (0, edge_items),
3615 print_options.precision,
3616 dims,
3617 );
3618 acc.push_str(", ...");
3619 display_fmt_inner(
3620 primitive,
3621 kind,
3622 acc,
3623 depth,
3624 multi_index,
3625 (dims[depth] - edge_items, dims[depth]),
3626 print_options.precision,
3627 dims,
3628 );
3629 } else {
3630 display_fmt_inner(
3631 primitive,
3632 kind,
3633 acc,
3634 depth,
3635 multi_index,
3636 (0, dims[depth]),
3637 print_options.precision,
3638 dims,
3639 );
3640 }
3641 } else if summarize && dims[depth] > 2 * edge_items {
3642 display_fmt_outer(
3643 primitive,
3644 kind,
3645 acc,
3646 depth,
3647 multi_index,
3648 print_options,
3649 summarize,
3650 (0, edge_items),
3651 dims,
3652 );
3653 acc.push(',');
3654 push_newline_indent_impl(acc, depth + 1);
3655 acc.push_str("...");
3656 push_newline_indent_impl(acc, depth + 1);
3657 display_fmt_outer(
3658 primitive,
3659 kind,
3660 acc,
3661 depth,
3662 multi_index,
3663 print_options,
3664 summarize,
3665 (dims[depth] - edge_items, dims[depth]),
3666 dims,
3667 );
3668 } else {
3669 display_fmt_outer(
3670 primitive,
3671 kind,
3672 acc,
3673 depth,
3674 multi_index,
3675 print_options,
3676 summarize,
3677 (0, dims[depth]),
3678 dims,
3679 );
3680 }
3681
3682 if depth == 0 {
3683 acc.push(']');
3684 }
3685}
3686
3687fn display_fmt_impl(
3688 primitive: &BridgeTensor,
3689 kind: Kind,
3690 kind_name: &str,
3691 f: &mut core::fmt::Formatter<'_>,
3692) -> core::fmt::Result {
3693 writeln!(f, "Tensor {{")?;
3694 {
3695 // Quantized tensors show metadata only (like candle): values are codes with scales,
3696 // so use `dequantize()` for values or `to_data()` for the stored codes.
3697 if primitive.is_qfloat() {
3698 writeln!(f, " data: <quantized, use .dequantize() to view values>,")?;
3699 } else {
3700 let mut po = { PRINT_OPTS.read().clone() };
3701 if let Some(precision) = f.precision() {
3702 po.precision = Some(precision);
3703 }
3704 let shape = primitive.shape();
3705 let dims: Vec<usize> = shape.iter().copied().collect();
3706 let mut acc = String::new();
3707 let mut multi_index = vec![0; dims.len()];
3708 let num_elements: usize = dims.iter().product();
3709 let summarize = num_elements > po.threshold;
3710 display_fmt_recursive(
3711 primitive,
3712 kind,
3713 &mut acc,
3714 0,
3715 &mut multi_index,
3716 &po,
3717 summarize,
3718 &dims,
3719 );
3720 writeln!(f, " data:")?;
3721 write!(f, "{acc}")?;
3722 writeln!(f, ",")?;
3723 }
3724 }
3725 writeln!(f, " shape: {},", primitive.shape())?;
3726 let device = match kind {
3727 Kind::Float => <Float as BasicOps>::device(primitive),
3728 Kind::Int => <Int as BasicOps>::device(primitive),
3729 Kind::Bool => <Bool as BasicOps>::device(primitive),
3730 };
3731 writeln!(f, " device: {:?},", device)?;
3732 writeln!(f, " kind: {:?},", kind_name)?;
3733 let dtype = primitive.dtype();
3734 writeln!(f, " dtype: {:?},", dtype.name())?;
3735 if let DType::QFloat(scheme) = dtype {
3736 writeln!(f, " scheme: {scheme:?},")?;
3737 }
3738 write!(f, "}}")
3739}
3740
3741fn try_into_data_sync_impl(
3742 primitive: BridgeTensor,
3743 kind: Kind,
3744) -> Result<TensorData, ExecutionError> {
3745 crate::try_read_sync(into_data_async_impl(primitive, kind)).expect(
3746 "Failed to read tensor data synchronously.
3747 This can happen on platforms that don't support blocking futures like WASM.
3748 If possible, try using into_data_async instead.",
3749 )
3750}
3751
3752#[cfg(test)]
3753mod tests {
3754 use super::*;
3755 use burn_std::SliceOps;
3756
3757 use crate::Slice;
3758 use crate::quantization::QuantScheme;
3759
3760 use crate::s;
3761
3762 #[test]
3763 fn scalar_read_returns_shape_error() {
3764 let error = Tensor::<1>::_unpack_scalar::<f32>(TensorData::from([1.0, 2.0])).unwrap_err();
3765
3766 assert!(matches!(
3767 error,
3768 TensorReadError::InvalidShape {
3769 expected: 1,
3770 actual: 2
3771 }
3772 ));
3773 }
3774
3775 #[test]
3776 fn scalar_read_converts_to_requested_element() {
3777 let scalar = Tensor::<1>::_unpack_scalar::<f32>(TensorData::from([3i32])).unwrap();
3778
3779 assert_eq!(scalar, 3.0);
3780 }
3781
3782 #[test]
3783 fn slice_range_single_dim_leading() {
3784 let shape = Shape::new([8, 4]);
3785
3786 // Half-open range
3787 let slices = shape.clone().into_slices([0..5]);
3788 assert_eq!(slices[0].to_range(8), 0..5);
3789 let slices = shape.clone().into_slices([-3..-1]);
3790 assert_eq!(slices[0].to_range(8), 5..7);
3791
3792 // Inclusive range
3793 let slices = shape.clone().into_slices([0..=4]);
3794 assert_eq!(slices[0].to_range(8), 0..5);
3795 let slices = shape.clone().into_slices([-2..=-1]);
3796 assert_eq!(slices[0].to_range(8), 6..8);
3797
3798 // Unbounded start
3799 let slices = shape.clone().into_slices([..3]);
3800 assert_eq!(slices[0].to_range(8), 0..3);
3801 let slices = shape.clone().into_slices([..-5]);
3802 assert_eq!(slices[0].to_range(8), 0..3);
3803
3804 // Unbounded end
3805 let slices = shape.clone().into_slices([5..]);
3806 assert_eq!(slices[0].to_range(8), 5..8);
3807 let slices = shape.clone().into_slices([-3..]);
3808 assert_eq!(slices[0].to_range(8), 5..8);
3809
3810 // Full range
3811 let slices = shape.into_slices([..]);
3812 assert_eq!(slices[0].to_range(8), 0..8);
3813 }
3814
3815 #[test]
3816 fn test_negative_slice_indices() {
3817 // Test negative indices conversion
3818 let slice: Slice = (-3..-1).into();
3819 assert_eq!(slice.start, -3);
3820 assert_eq!(slice.end, Some(-1));
3821
3822 // Test to_range conversion with size 8
3823 let range = slice.to_range(8);
3824 assert_eq!(range, 5..7);
3825
3826 // Test with shape slice
3827 let shape = Shape::new([8, 4]);
3828 let result = shape.clone().into_slices([-3..-1]);
3829 assert_eq!(result[0].to_range(8), 5..7);
3830
3831 // Test more negative index cases
3832 let slice2: Slice = (-5..).into();
3833 assert_eq!(slice2.to_range(10), 5..10);
3834
3835 let slice3: Slice = (..-2).into();
3836 assert_eq!(slice3.to_range(10), 0..8);
3837
3838 // Test with s! macro - single dimension returns Slice directly
3839 let slice4 = s![-3..-1];
3840 assert_eq!(slice4.start, -3);
3841 assert_eq!(slice4.end, Some(-1));
3842 }
3843
3844 #[test]
3845 fn slice_range_multi_dim() {
3846 let shape = Shape::new([8, 4]);
3847
3848 // Multiple ways to provide ranges
3849 let slices = shape.clone().into_slices([0..5, 0..4]);
3850 assert_eq!(slices[0].to_range(8), 0..5);
3851 assert_eq!(slices[1].to_range(4), 0..4);
3852
3853 let slices = shape.clone().into_slices([0.., 0..]);
3854 assert_eq!(slices[0].to_range(8), 0..8);
3855 assert_eq!(slices[1].to_range(4), 0..4);
3856
3857 let slices = shape.clone().into_slices([0..=7, 0..=3]);
3858 assert_eq!(slices[0].to_range(8), 0..8);
3859 assert_eq!(slices[1].to_range(4), 0..4);
3860
3861 let slices = shape.clone().into_slices([0..5, 0..3]);
3862 assert_eq!(slices[0].to_range(8), 0..5);
3863 assert_eq!(slices[1].to_range(4), 0..3);
3864
3865 let slices = shape.into_slices([0.., 0..]);
3866 assert_eq!(slices[0].to_range(8), 0..8);
3867 assert_eq!(slices[1].to_range(4), 0..4);
3868 }
3869
3870 #[test]
3871 fn slice_range_multi_dim_index() {
3872 let shape = Shape::new([8, 4]);
3873
3874 // Indices (single integer) should also convert to correct range
3875 let slices = shape.clone().into_slices([0, 2]);
3876 assert_eq!(slices[0].to_range(8), 0..1);
3877 assert_eq!(slices[1].to_range(4), 2..3);
3878
3879 let slices = shape.into_slices([-1, -1]);
3880 assert_eq!(slices[0].to_range(8), 7..8);
3881 assert_eq!(slices[1].to_range(4), 3..4);
3882 }
3883
3884 #[test]
3885 fn slice_range_multi_dim_heterogeneous() {
3886 // Slice macro `s![]` can be used to provide different range types
3887 let shape = Shape::new([8, 4, 2]);
3888 let slice = s![0..5, .., -1];
3889 let slices = shape.into_slices(slice);
3890 assert_eq!(slices[0].to_range(8), 0..5);
3891 assert_eq!(slices[1].to_range(4), 0..4);
3892 assert_eq!(slices[2].to_range(2), 1..2);
3893
3894 let shape = Shape::new([8, 4, 2, 3]);
3895 let slice = s![..=4, 0..=3, .., -2..];
3896 let slices = shape.into_slices(slice);
3897 assert_eq!(slices[0].to_range(8), 0..5);
3898 assert_eq!(slices[1].to_range(4), 0..4);
3899 assert_eq!(slices[2].to_range(2), 0..2);
3900 assert_eq!(slices[3].to_range(3), 1..3);
3901
3902 let shape = Shape::new([3, 4]);
3903 let slice = s![1..-1, ..];
3904 let slices = shape.into_slices(slice);
3905 assert_eq!(slices[0].to_range(3), 1..2);
3906 assert_eq!(slices[1].to_range(4), 0..4);
3907 }
3908
3909 #[test]
3910 fn data_iter_fmt_formats_quantized_as_placeholder() {
3911 let data = TensorData::quantized(
3912 vec![-127i8, 0, 64, 127],
3913 [4],
3914 QuantScheme::default(),
3915 &[0.1],
3916 None,
3917 );
3918 assert_eq!(
3919 DataIterFmt {
3920 data,
3921 precision: None
3922 }
3923 .next(),
3924 "<quantized>"
3925 );
3926 }
3927}