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