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