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