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