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