Skip to main content

burn_tensor/tensor/api/
base.rs

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