Skip to main content

burn_tensor/tensor/api/
base.rs

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