Skip to main content

burn_tensor/tensor/api/
base.rs

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