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