Skip to main content

hanzo_ml/
tensor.rs

1//! Tensors are N-dimensional matrixes of elements using a single data type.
2#![allow(clippy::redundant_closure_call)]
3use crate::backend::{BackendDevice, BackendStorage};
4use crate::op::{BackpropOp, BinaryOp, CmpOp, Op, ReduceOp, UnaryOp};
5use crate::scalar::TensorOrScalar;
6use crate::shape::{Dim, Dims, ShapeWithOneHole};
7use crate::{bail, storage::Storage, DType, Device, Error, Layout, Result, Shape};
8use std::sync::{Arc, RwLock};
9
10/// Unique identifier for tensors.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub struct TensorId(usize);
13
14impl TensorId {
15    fn new() -> Self {
16        // https://users.rust-lang.org/t/idiomatic-rust-way-to-generate-unique-id/33805
17        use std::sync::atomic;
18        static COUNTER: atomic::AtomicUsize = atomic::AtomicUsize::new(1);
19        Self(COUNTER.fetch_add(1, atomic::Ordering::Relaxed))
20    }
21}
22
23pub struct Tensor_ {
24    id: TensorId,
25    // As we provide inner mutability on the tensor content, the alternatives are:
26    // - Using a mutex, this would have the highest cost when retrieving the storage but would
27    //   prevent errors when concurrent access takes place. Mutex would also be subject to
28    //   deadlocks for example using the current code if the same tensor is used twice by a single
29    //   binary op.
30    // - Using a refcell unsafe cell would have some intermediary cost, borrow checking would be
31    //   verified dynamically, but the resulting tensors would not be send or sync.
32    // - Using an unsafe cell would have the lowest cost but undefined behavior on concurrent
33    //   accesses.
34    // Ideally, we would use Arc<Storage> for tensors on which we don't plan on modifying the data
35    // and Arc<Mutex<Storage>> for tensors where the data could be modified, e.g. variables but
36    // that's tricky to encode in the current setup.
37    storage: Arc<RwLock<Storage>>,
38    layout: Layout,
39    op: BackpropOp,
40    is_variable: bool,
41    dtype: DType,
42    device: Device,
43}
44
45impl AsRef<Tensor> for Tensor {
46    fn as_ref(&self) -> &Tensor {
47        self
48    }
49}
50
51// Tensors are refcounted so that cloning is cheap when building the op graph.
52// Storages are also refcounted independently so that its possible to avoid
53// copying the storage for operations that only modify the shape or stride.
54#[derive(Clone)]
55/// The core struct for manipulating tensors.
56///
57/// ```rust
58/// use hanzo_ml::{Tensor, DType, Device};
59///
60/// let a = Tensor::arange(0f32, 6f32, &Device::Cpu)?.reshape((2, 3))?;
61/// let b = Tensor::arange(0f32, 12f32, &Device::Cpu)?.reshape((3, 4))?;
62///
63/// let c = a.matmul(&b)?;
64/// # Ok::<(), hanzo_ml::Error>(())
65/// ```
66///
67/// Tensors are reference counted with [`Arc`] so cloning them is cheap.
68pub struct Tensor(Arc<Tensor_>);
69
70impl std::ops::Deref for Tensor {
71    type Target = Tensor_;
72
73    fn deref(&self) -> &Self::Target {
74        self.0.as_ref()
75    }
76}
77
78macro_rules! unary_op {
79    ($fn_name:ident, $op_name:ident) => {
80        pub fn $fn_name(&self) -> Result<Self> {
81            let shape = self.shape();
82            if shape.elem_count() == 0 {
83                return Ok(self.clone());
84            }
85            let storage = self
86                .storage()
87                .unary_impl::<crate::op::$op_name>(self.layout())?;
88            let op = BackpropOp::new1(self, |s| Op::Unary(s, UnaryOp::$op_name));
89            Ok(from_storage(storage, shape.clone(), op, false))
90        }
91    };
92}
93
94macro_rules! binary_op {
95    ($fn_name:ident, $op_name:ident) => {
96        pub fn $fn_name(&self, rhs: &Self) -> Result<Self> {
97            let shape = self.same_shape_binary_op(rhs, stringify!($fn_name))?;
98            if shape.elem_count() == 0 {
99                return Ok(self.clone());
100            }
101            let storage = self.storage().binary_impl::<crate::op::$op_name>(
102                &*rhs.storage(),
103                self.layout(),
104                rhs.layout(),
105            )?;
106            let op = BackpropOp::new2(self, rhs, |t1, t2| Op::Binary(t1, t2, BinaryOp::$op_name));
107            Ok(from_storage(storage, shape.clone(), op, false))
108        }
109    };
110}
111
112macro_rules! binary_op_scalar {
113    ($fn_name:ident, $op_name:ident) => {
114        pub fn $fn_name<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
115            let rhs = match rhs.to_tensor_scalar()? {
116                crate::scalar::TensorScalar::Tensor(rhs) => rhs,
117                crate::scalar::TensorScalar::Scalar(rhs) => rhs
118                    .to_dtype(self.dtype())?
119                    .to_device(self.device())?
120                    .broadcast_as(self.shape())?,
121            };
122            let shape = self.same_shape_binary_op(&rhs, stringify!($fn_name))?;
123            if self.elem_count() == 0 {
124                return Ok(self.clone());
125            }
126            let storage = self.storage().binary_impl::<crate::op::$op_name>(
127                &*rhs.storage(),
128                self.layout(),
129                rhs.layout(),
130            )?;
131            let op = BackpropOp::new2(self, &rhs, |t1, t2| Op::Binary(t1, t2, BinaryOp::$op_name));
132            Ok(from_storage(storage, shape.clone(), op, false))
133        }
134    };
135}
136
137macro_rules! broadcast_binary_op {
138    ($fn_name:ident, $inner_fn_name:ident) => {
139        pub fn $fn_name(&self, rhs: &Self) -> Result<Self> {
140            let lhs = self;
141            let shape = lhs
142                .shape()
143                .broadcast_shape_binary_op(rhs.shape(), stringify!($fn_name))?;
144            let l_broadcast = shape != *lhs.shape();
145            let r_broadcast = shape != *rhs.shape();
146            match (l_broadcast, r_broadcast) {
147                (true, true) => lhs
148                    .broadcast_as(&shape)?
149                    .$inner_fn_name(&rhs.broadcast_as(&shape)?),
150                (false, true) => lhs.$inner_fn_name(&rhs.broadcast_as(&shape)?),
151                (true, false) => lhs.broadcast_as(&shape)?.$inner_fn_name(rhs),
152                (false, false) => lhs.$inner_fn_name(rhs),
153            }
154        }
155    };
156}
157
158/// Creates a fresh tensor structure based on a storage and a shape, this uses contiguous strides.
159pub(crate) fn from_storage<S: Into<Shape>>(
160    storage: Storage,
161    shape: S,
162    op: BackpropOp,
163    is_variable: bool,
164) -> Tensor {
165    let dtype = storage.dtype();
166    let device = storage.device();
167    let tensor_ = Tensor_ {
168        id: TensorId::new(),
169        storage: Arc::new(RwLock::new(storage)),
170        layout: Layout::contiguous(shape),
171        op,
172        is_variable,
173        dtype,
174        device,
175    };
176    Tensor(Arc::new(tensor_))
177}
178
179impl Tensor {
180    pub(crate) fn ones_impl<S: Into<Shape>>(
181        shape: S,
182        dtype: DType,
183        device: &Device,
184        is_variable: bool,
185    ) -> Result<Self> {
186        let none = BackpropOp::none();
187        let shape = shape.into();
188        let mut storage = unsafe { device.alloc_uninit(&shape, dtype)? };
189        let layout = Layout::contiguous(shape.clone());
190        storage.const_set(crate::scalar::Scalar::one(dtype), &layout)?;
191        Ok(from_storage(storage, shape, none, is_variable))
192    }
193
194    /// Creates a new tensor filled with ones.
195    ///
196    /// ```rust
197    /// use hanzo_ml::{Tensor, DType, Device};
198    /// let a = Tensor::ones((2, 3), DType::F32, &Device::Cpu)?;
199    /// let b = Tensor::from_slice(&[1.0f32, 1.0, 1.0, 1.0, 1.0, 1.0], (2, 3), &Device::Cpu)?;
200    /// // a == b
201    /// # Ok::<(), hanzo_ml::Error>(())
202    /// ```
203    pub fn ones<S: Into<Shape>>(shape: S, dtype: DType, device: &Device) -> Result<Self> {
204        Self::ones_impl(shape, dtype, device, false)
205    }
206
207    pub fn const_set(&self, value: crate::scalar::Scalar) -> Result<()> {
208        self.storage_mut().const_set(value, self.layout())
209    }
210
211    pub fn zero_set(&self) -> Result<()> {
212        self.const_set(crate::scalar::Scalar::zero(self.dtype()))
213    }
214
215    pub fn one_set(&self) -> Result<()> {
216        self.const_set(crate::scalar::Scalar::one(self.dtype()))
217    }
218
219    /// Creates a new tensor filled with ones with same shape, dtype, and device as the other tensor.
220    ///
221    /// ```rust
222    /// use hanzo_ml::{Tensor, DType, Device};
223    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
224    /// let b = a.ones_like()?;
225    /// // b == a + 1
226    /// # Ok::<(), hanzo_ml::Error>(())
227    /// ```
228    pub fn ones_like(&self) -> Result<Self> {
229        Tensor::ones(self.shape(), self.dtype(), self.device())
230    }
231
232    // Do not expose outside of the crate, the `is_variable=true` case should only be accessed from
233    // the variable module.
234    pub(crate) fn zeros_impl<S: Into<Shape>>(
235        shape: S,
236        dtype: DType,
237        device: &Device,
238        is_variable: bool,
239    ) -> Result<Self> {
240        let none = BackpropOp::none();
241        let shape = shape.into();
242        let storage = device.zeros(&shape, dtype)?;
243        Ok(from_storage(storage, shape, none, is_variable))
244    }
245
246    /// Creates a new tensor filled with zeros.
247    ///
248    /// ```rust
249    /// use hanzo_ml::{Tensor, DType, Device};
250    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
251    /// let b = Tensor::from_slice(&[0.0f32, 0.0, 0.0, 0.0, 0.0, 0.0], (2, 3), &Device::Cpu)?;
252    /// // a == b
253    /// # Ok::<(), hanzo_ml::Error>(())
254    /// ```
255    pub fn zeros<S: Into<Shape>>(shape: S, dtype: DType, device: &Device) -> Result<Self> {
256        Self::zeros_impl(shape, dtype, device, false)
257    }
258
259    /// Creates a new tensor filled with zeros with same shape, dtype, and device as the other
260    /// tensor.
261    ///
262    /// ```rust
263    /// use hanzo_ml::{Tensor, DType, Device};
264    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
265    /// let b = a.zeros_like()?;
266    /// // b is on CPU f32.
267    /// # Ok::<(), hanzo_ml::Error>(())
268    /// ```
269    pub fn zeros_like(&self) -> Result<Self> {
270        Tensor::zeros(self.shape(), self.dtype(), self.device())
271    }
272
273    // Do not expose outside of the crate, the `is_variable=true` case should only be accessed from
274    // the variable module.
275    pub(crate) unsafe fn empty_impl<S: Into<Shape>>(
276        shape: S,
277        dtype: DType,
278        device: &Device,
279        is_variable: bool,
280    ) -> Result<Self> {
281        let none = BackpropOp::none();
282        let shape = shape.into();
283        let storage = device.alloc_uninit(&shape, dtype)?;
284        Ok(from_storage(storage, shape, none, is_variable))
285    }
286
287    /// Creates a new tensor filled with uninitialized memory.
288    ///
289    /// # Safety
290    /// This returns uninitialized memory.
291    ///
292    /// ```rust
293    /// use hanzo_ml::{Tensor, DType, Device};
294    /// let a = unsafe { Tensor::empty((2, 3), DType::F32, &Device::Cpu)? };
295    /// // a == b
296    /// # Ok::<(), hanzo_ml::Error>(())
297    /// ```
298    pub unsafe fn empty<S: Into<Shape>>(shape: S, dtype: DType, device: &Device) -> Result<Self> {
299        Self::empty_impl(shape, dtype, device, false)
300    }
301
302    /// Creates a new tensor filled with uninitialized memory of the same shape, dtype, and device as the other
303    /// tensor.
304    ///
305    /// # Safety
306    /// This returns uninitialized memory.
307    ///
308    /// ```rust
309    /// use hanzo_ml::{Tensor, DType, Device};
310    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
311    /// let b = unsafe { a.empty_like()? };
312    /// # Ok::<(), hanzo_ml::Error>(())
313    /// ```
314    pub unsafe fn empty_like(&self) -> Result<Self> {
315        Tensor::empty(self.shape(), self.dtype(), self.device())
316    }
317
318    pub(crate) fn rand_impl<S: Into<Shape>, T: crate::FloatDType>(
319        lo: T,
320        up: T,
321        s: S,
322        device: &Device,
323        is_variable: bool,
324    ) -> Result<Self> {
325        let s = s.into();
326        let storage = device.rand_uniform(lo, up, &s)?;
327        let none = BackpropOp::none();
328        Ok(from_storage(storage, s, none, is_variable))
329    }
330
331    pub(crate) fn rand_f64_impl<S: Into<Shape>>(
332        lo: f64,
333        up: f64,
334        s: S,
335        dtype: DType,
336        device: &Device,
337        is_variable: bool,
338    ) -> Result<Self> {
339        let s = s.into();
340        let storage = device.rand_uniform_f64(lo, up, &s, dtype)?;
341        let none = BackpropOp::none();
342        Ok(from_storage(storage, s, none, is_variable))
343    }
344
345    /// Creates a new tensor initialized with values sampled uniformly between `lo` and `up`.
346    pub fn rand<S: Into<Shape>, T: crate::FloatDType>(
347        lo: T,
348        up: T,
349        s: S,
350        device: &Device,
351    ) -> Result<Self> {
352        Self::rand_impl(lo, up, s, device, false)
353    }
354
355    pub fn rand_like(&self, lo: f64, up: f64) -> Result<Self> {
356        Tensor::rand_f64_impl(lo, up, self.shape(), self.dtype(), self.device(), false)
357    }
358
359    pub(crate) fn randn_impl<S: Into<Shape>, T: crate::FloatDType>(
360        mean: T,
361        std: T,
362        s: S,
363        device: &Device,
364        is_variable: bool,
365    ) -> Result<Self> {
366        let s = s.into();
367        let storage = device.rand_normal(mean, std, &s)?;
368        let none = BackpropOp::none();
369        Ok(from_storage(storage, s, none, is_variable))
370    }
371
372    pub(crate) fn randn_f64_impl<S: Into<Shape>>(
373        mean: f64,
374        std: f64,
375        s: S,
376        dtype: DType,
377        device: &Device,
378        is_variable: bool,
379    ) -> Result<Self> {
380        let s = s.into();
381        let storage = device.rand_normal_f64(mean, std, &s, dtype)?;
382        let none = BackpropOp::none();
383        Ok(from_storage(storage, s, none, is_variable))
384    }
385
386    pub fn randn_like(&self, mean: f64, stdev: f64) -> Result<Self> {
387        Tensor::randn_f64_impl(
388            mean,
389            stdev,
390            self.shape(),
391            self.dtype(),
392            self.device(),
393            false,
394        )
395    }
396
397    /// Creates a new tensor initialized with values sampled from a normal distribution with the
398    /// specified `mean` and standard deviation `std`.
399    pub fn randn<S: Into<Shape>, T: crate::FloatDType>(
400        mean: T,
401        std: T,
402        s: S,
403        device: &Device,
404    ) -> Result<Self> {
405        Self::randn_impl(mean, std, s, device, false)
406    }
407
408    pub(crate) fn new_impl<A: crate::device::NdArray>(
409        array: A,
410        shape: Shape,
411        device: &Device,
412        is_variable: bool,
413    ) -> Result<Self> {
414        let n: usize = shape.elem_count();
415        let buffer_size: usize = array.shape()?.elem_count();
416        if buffer_size != n {
417            return Err(Error::ShapeMismatch { buffer_size, shape }.bt());
418        }
419        let storage = device.storage(array)?;
420        let none = BackpropOp::none();
421        Ok(from_storage(storage, shape, none, is_variable))
422    }
423
424    /// Creates a new tensor on the specified device using the content and shape of the input.
425    pub fn new<A: crate::device::NdArray>(array: A, device: &Device) -> Result<Self> {
426        let shape = array.shape()?;
427        Self::new_impl(array, shape, device, false)
428    }
429
430    /// Returns a new tensor with all the elements having the same specified value.
431    ///```rust
432    /// use hanzo_ml::{Tensor, Device};
433    /// let a = Tensor::full(3.5, (2, 4), &Device::Cpu)?;
434    ///
435    /// assert_eq!(a.to_vec2::<f64>()?, &[
436    ///     [3.5, 3.5, 3.5, 3.5],
437    ///     [3.5, 3.5, 3.5, 3.5],
438    /// ]);
439    /// # Ok::<(), hanzo_ml::Error>(())
440    pub fn full<D: crate::WithDType, S: Into<Shape>>(
441        value: D,
442        shape: S,
443        device: &Device,
444    ) -> Result<Self> {
445        let none = BackpropOp::none();
446        let shape = shape.into();
447        let mut storage = unsafe { device.alloc_uninit(&shape, D::DTYPE)? };
448        let layout = Layout::contiguous(shape.clone());
449        storage.const_set(value.to_scalar(), &layout)?;
450        Ok(from_storage(storage, shape, none, false))
451    }
452
453    /// Creates a new 1D tensor from an iterator.
454    ///```rust
455    /// use hanzo_ml::{Tensor, Device};
456    /// let a = Tensor::from_iter( [1.0, 2.0, 3.0, 4.0].into_iter(), &Device::Cpu)?;
457    ///
458    /// assert_eq!(a.to_vec1::<f64>()?, &[1.0, 2.0, 3.0, 4.0]);
459    /// # Ok::<(), hanzo_ml::Error>(())
460    /// ```
461    pub fn from_iter<D: crate::WithDType>(
462        iter: impl IntoIterator<Item = D>,
463        device: &Device,
464    ) -> Result<Self> {
465        let data = iter.into_iter().collect::<Vec<_>>();
466        let len = data.len();
467        Self::from_vec_impl(data, len, device, false)
468    }
469
470    /// Creates a new 1D tensor with values from the interval `[start, end)` taken with a common
471    /// difference `1` from `start`.
472    ///```rust
473    /// use hanzo_ml::{Tensor, Device};
474    /// let a = Tensor::arange(2., 5., &Device::Cpu)?;
475    ///
476    /// assert_eq!(a.to_vec1::<f64>()?, &[2., 3., 4.]);
477    /// # Ok::<(), hanzo_ml::Error>(())
478    /// ```
479    pub fn arange<D: crate::WithDType>(start: D, end: D, device: &Device) -> Result<Self> {
480        Self::arange_step(start, end, D::one(), device)
481    }
482
483    /// Creates a new 1D tensor with values from the interval `[start, end)` taken with a common
484    /// difference `step` from `start`.
485    ///```rust
486    /// use hanzo_ml::{Tensor, Device};
487    /// let a = Tensor::arange_step(2.0, 4.0, 0.5, &Device::Cpu)?;
488    ///
489    /// assert_eq!(a.to_vec1::<f64>()?, &[2.0, 2.5, 3.0, 3.5]);
490    /// # Ok::<(), hanzo_ml::Error>(())
491    /// ```
492    pub fn arange_step<D: crate::WithDType>(
493        start: D,
494        end: D,
495        step: D,
496        device: &Device,
497    ) -> Result<Self> {
498        if D::is_zero(&step) {
499            bail!("step cannot be zero")
500        }
501        let mut data = vec![];
502        let mut current = start;
503        if step >= D::zero() {
504            while current < end {
505                data.push(current);
506                current += step;
507            }
508        } else {
509            while current > end {
510                data.push(current);
511                current += step;
512            }
513        }
514        let len = data.len();
515        Self::from_vec_impl(data, len, device, false)
516    }
517
518    pub(crate) fn from_vec_impl<S: ShapeWithOneHole, D: crate::WithDType>(
519        data: Vec<D>,
520        shape: S,
521        device: &Device,
522        is_variable: bool,
523    ) -> Result<Self> {
524        let shape = shape.into_shape(data.len())?;
525        let storage = device.storage_owned(data)?;
526        let none = BackpropOp::none();
527        Ok(from_storage(storage, shape, none, is_variable))
528    }
529
530    /// Creates a new tensor initialized with values from the input vector. The number of elements
531    /// in this vector must be the same as the number of elements defined by the shape.
532    /// If the device is cpu, no data copy is made.
533    ///```rust
534    /// use hanzo_ml::{Tensor, Device};
535    /// let a = Tensor::from_vec(vec!{1., 2., 3., 4., 5., 6.}, (2, 3), &Device::Cpu)?;
536    ///
537    /// assert_eq!(a.to_vec2::<f64>()?, &[
538    ///     [1., 2., 3.],
539    ///     [4., 5., 6.]
540    /// ]);
541    /// # Ok::<(), hanzo_ml::Error>(())
542    /// ```
543    pub fn from_vec<S: ShapeWithOneHole, D: crate::WithDType>(
544        data: Vec<D>,
545        shape: S,
546        device: &Device,
547    ) -> Result<Self> {
548        Self::from_vec_impl(data, shape, device, false)
549    }
550
551    /// Creates a new tensor initialized with values from the input slice. The number of elements
552    /// in this vector must be the same as the number of elements defined by the shape.
553    ///```rust
554    /// use hanzo_ml::{Tensor, Device};
555    /// let values = vec![1., 2., 3., 4., 5., 6., 7., 8.];
556    /// let a = Tensor::from_slice(&values[1..7], (2, 3), &Device::Cpu)?;
557    ///
558    /// assert_eq!(a.to_vec2::<f64>()?, &[
559    ///     [2., 3., 4.],
560    ///     [5., 6., 7.]
561    /// ]);
562    /// # Ok::<(), hanzo_ml::Error>(())
563    /// ```
564    pub fn from_slice<S: ShapeWithOneHole, D: crate::WithDType>(
565        array: &[D],
566        shape: S,
567        device: &Device,
568    ) -> Result<Self> {
569        let shape = shape.into_shape(array.len())?;
570        let storage = device.storage_from_slice(array)?;
571        let none = BackpropOp::none();
572        Ok(from_storage(storage, shape, none, false))
573    }
574
575    pub(crate) fn same_shape_binary_op(&self, rhs: &Self, op: &'static str) -> Result<&Shape> {
576        let lhs = self.shape();
577        let rhs = rhs.shape();
578        if lhs != rhs {
579            Err(Error::ShapeMismatchBinaryOp {
580                lhs: lhs.clone(),
581                rhs: rhs.clone(),
582                op,
583            }
584            .bt())
585        } else {
586            Ok(lhs)
587        }
588    }
589
590    /// Returns true if the computation graph should track this op, that is if it is
591    /// a variable or if it has some variable as dependencies.
592    pub fn track_op(&self) -> bool {
593        self.is_variable || self.op.is_some()
594    }
595
596    /// Creates a fresh tensor structure based on a storage and a shape.
597    ///
598    /// # Note
599    /// - This uses contiguous strides
600    /// - Ensure the shape is compatible with the shape of the storage.
601    pub fn from_storage<S: Into<Shape>>(
602        storage: Storage,
603        shape: S,
604        op: BackpropOp,
605        is_variable: bool,
606    ) -> Tensor {
607        from_storage(storage, shape, op, is_variable)
608    }
609
610    // TODO: Also make an inplace version or a pre-allocated? This could be tricky
611    // if this can create cycles in the compute graph.
612    binary_op!(add, Add);
613    binary_op!(mul, Mul);
614    binary_op!(sub, Sub);
615    binary_op!(div, Div);
616    binary_op_scalar!(maximum, Maximum);
617    binary_op_scalar!(minimum, Minimum);
618    broadcast_binary_op!(broadcast_add, add);
619    broadcast_binary_op!(broadcast_mul, mul);
620    broadcast_binary_op!(broadcast_sub, sub);
621    broadcast_binary_op!(broadcast_div, div);
622    broadcast_binary_op!(broadcast_maximum, maximum);
623    broadcast_binary_op!(broadcast_minimum, minimum);
624    broadcast_binary_op!(broadcast_eq, eq);
625    broadcast_binary_op!(broadcast_ne, ne);
626    broadcast_binary_op!(broadcast_lt, lt);
627    broadcast_binary_op!(broadcast_le, le);
628    broadcast_binary_op!(broadcast_gt, gt);
629    broadcast_binary_op!(broadcast_ge, ge);
630
631    unary_op!(recip, Recip);
632    unary_op!(neg, Neg);
633    unary_op!(exp, Exp);
634    unary_op!(log, Log);
635    unary_op!(sin, Sin);
636    unary_op!(cos, Cos);
637    unary_op!(tanh, Tanh);
638    unary_op!(abs, Abs);
639    unary_op!(sqr, Sqr);
640    unary_op!(sqrt, Sqrt);
641    unary_op!(gelu, Gelu);
642    unary_op!(gelu_erf, GeluErf);
643    unary_op!(erf, Erf);
644    unary_op!(relu, Relu);
645    unary_op!(silu, Silu);
646    unary_op!(ceil, Ceil);
647    unary_op!(floor, Floor);
648    unary_op!(round, Round);
649    unary_op!(sign, Sign);
650
651    /// Round element of the input tensor to the nearest integer.
652    ///
653    /// If the number of decimals is negative, it specifies the number of positions to the left of
654    /// the decimal point.
655    pub fn round_to(&self, decimals: i32) -> Result<Self> {
656        let mult = 10f64.powi(decimals);
657        (self * mult)?.round()? * (1f64 / mult)
658    }
659
660    /// Retrieves the single scalar value hold in the tensor. If the tensor contains multiple
661    /// dimensions, an error is returned instead.
662    pub fn to_scalar<S: crate::WithDType>(&self) -> Result<S> {
663        if self.rank() != 0 {
664            Err(Error::UnexpectedNumberOfDims {
665                expected: 0,
666                got: self.rank(),
667                shape: self.shape().clone(),
668            }
669            .bt())?
670        }
671        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
672            let data = S::cpu_storage_as_slice(cpu_storage)?;
673            Ok::<_, Error>(data[self.layout().start_offset()])
674        };
675        match &*self.storage() {
676            Storage::Cpu(cpu_storage) => from_cpu_storage(cpu_storage),
677            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
678            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
679            #[cfg(feature = "rocm")]
680            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
681            #[cfg(feature = "vulkan")]
682            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
683            #[cfg(feature = "wgpu")]
684            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
685        }
686    }
687
688    /// An alias for `to_scalar`.
689    pub fn to_vec0<S: crate::WithDType>(&self) -> Result<S> {
690        self.to_scalar::<S>()
691    }
692
693    /// Repeat this tensor along the specified dimensions.
694    pub fn repeat<S: Into<Shape>>(&self, shape: S) -> Result<Tensor> {
695        // Similar to PyTorch, we extend the number of dimensions of self if needed.
696        let repeats = shape.into();
697        let repeats = repeats.dims();
698        let mut inp = if self.rank() < repeats.len() {
699            let shape = [vec![1; repeats.len() - self.rank()], self.dims().to_vec()].concat();
700            self.reshape(shape)?
701        } else {
702            self.clone()
703        };
704        for (idx, &repeat) in repeats.iter().enumerate() {
705            if repeat > 1 {
706                inp = Tensor::cat(&vec![&inp; repeat], idx)?
707            }
708        }
709        Ok(inp)
710    }
711
712    /// Creates grids of coordinates specified by the 1D inputs.
713    ///
714    /// # Arguments
715    ///
716    /// * `args` - A slice of 1D tensors.
717    /// * `xy_indexing` - Whether to use xy indexing or ij indexing. If xy is selected, the
718    ///   first dimension corresponds to the cardinality of the second input and the second
719    ///   dimension corresponds to the cardinality of the first input. If ij is selected, the
720    ///   dimensions are in the same order as the cardinality of the inputs.
721    ///
722    /// # Examples
723    ///
724    /// ```rust
725    /// use hanzo_ml::{Tensor, Device, Shape};
726    /// let x = Tensor::new(&[1f32, 2., 3.], &Device::Cpu)?;
727    /// let y = Tensor::new(&[4f32, 5., 6.], &Device::Cpu)?;
728    ///
729    /// let grids_xy = Tensor::meshgrid(&[&x, &y], true)?;
730    ///
731    /// assert_eq!(grids_xy.len(), 2);
732    /// assert_eq!(grids_xy[0].dims(), &[3, 3]);
733    ///
734    /// assert_eq!(grids_xy[0].to_vec2::<f32>()?, &[[1., 2., 3.], [1., 2., 3.], [1., 2., 3.]]);
735    /// assert_eq!(grids_xy[1].to_vec2::<f32>()?, &[[4., 4., 4.], [5., 5., 5.], [6., 6., 6.]]);
736    ///
737    /// let grids_ij = Tensor::meshgrid(&[&x, &y], false)?;
738    ///
739    /// assert_eq!(grids_ij[0].to_vec2::<f32>()?, &[[1., 1., 1.], [2., 2., 2.], [3., 3., 3.]]);
740    /// assert_eq!(grids_ij[1].to_vec2::<f32>()?, &[[4., 5., 6.], [4., 5., 6.], [4., 5., 6.]]);
741    /// # Ok::<(), hanzo_ml::Error>(())
742    /// ```
743    ///
744    /// # Errors
745    ///
746    /// * Will return `Err` if `args` contains less than 2 tensors.
747    ///
748    pub fn meshgrid<A: AsRef<Tensor>>(args: &[A], xy_indexing: bool) -> Result<Vec<Self>> {
749        if args.len() <= 1 {
750            Err(Error::OpRequiresAtLeastTwoTensors { op: "meshgrid" }.bt())?
751        }
752        let args: Vec<_> = if xy_indexing {
753            args.iter().rev().collect()
754        } else {
755            args.iter().collect()
756        };
757
758        let mut shape = Vec::with_capacity(args.len());
759        for arg in args.iter() {
760            shape.push(arg.as_ref().dims1()?)
761        }
762
763        let mut grids = Vec::with_capacity(args.len());
764        for idx in 0..args.len() {
765            let mut ones = vec![1usize; args.len()];
766            ones[idx] = shape[idx];
767            let arg = args[idx].as_ref().reshape(ones)?;
768            let mut repeats = shape.clone();
769            repeats[idx] = 1;
770            let repeated_tensor = arg.repeat(repeats)?;
771            grids.push(repeated_tensor);
772        }
773        if xy_indexing {
774            grids.reverse();
775        }
776        Ok(grids)
777    }
778
779    /// This operation multiplies the input tensor by `mul` then adds `add` and return the result.
780    /// The input values `mul` and `add` are casted to the appropriate type so some rounding might
781    /// be performed.
782    ///
783    /// ```rust
784    /// use hanzo_ml::{Tensor, Device};
785    /// let a = Tensor::new(&[[0f32, 1.], [2., 3.]], &Device::Cpu)?;
786    /// let a = a.affine(4., -2.)?;
787    /// assert_eq!(a.to_vec2::<f32>()?, &[[-2.0, 2.0], [6.0, 10.0]]);
788    /// # Ok::<(), hanzo_ml::Error>(())
789    /// ```
790    pub fn affine(&self, mul: f64, add: f64) -> Result<Self> {
791        if self.elem_count() == 0 {
792            return Ok(self.clone());
793        }
794        let storage = self.storage().affine(self.layout(), mul, add)?;
795        let op = BackpropOp::new1(self, |arg| Op::Affine { arg, mul, add });
796        Ok(from_storage(storage, self.shape(), op, false))
797    }
798
799    /// Applies the Exponential Linear Unit (ELU) function on each element of the input tensor.
800    pub fn elu(&self, alpha: f64) -> Result<Self> {
801        if self.elem_count() == 0 {
802            return Ok(self.clone());
803        }
804        let storage = self.storage().elu(self.layout(), alpha)?;
805        let op = BackpropOp::new1(self, |t| Op::Elu(t, alpha));
806        Ok(from_storage(storage, self.shape(), op, false))
807    }
808
809    /// Raise the tensor to some float exponent `e`.
810    pub fn powf(&self, e: f64) -> Result<Self> {
811        if self.elem_count() == 0 {
812            return Ok(self.clone());
813        }
814        let storage = self.storage().powf(self.layout(), e)?;
815        let op = BackpropOp::new1(self, |t| Op::Powf(t, e));
816        Ok(from_storage(storage, self.shape(), op, false))
817    }
818
819    pub(crate) fn check_dim(&self, dim: usize, op: &'static str) -> Result<()> {
820        if dim >= self.dims().len() {
821            Err(Error::DimOutOfRange {
822                shape: self.shape().clone(),
823                dim: dim as i32,
824                op,
825            }
826            .bt())?
827        } else {
828            Ok(())
829        }
830    }
831
832    /// Split a tensor into the specified number of chunks, this may return less chunks than
833    /// specified.
834    pub fn chunk<D: Dim>(&self, chunks: usize, dim: D) -> Result<Vec<Self>> {
835        let dim = dim.to_index(self.shape(), "chunk")?;
836        let size = self.dim(dim)?;
837        if size < chunks {
838            (0..size).map(|i| self.narrow(dim, i, 1)).collect()
839        } else {
840            let chunk_size = size / chunks;
841            let cnt_additional = size % chunks;
842            let mut tensors = vec![];
843            let mut sum_chunk_size = 0;
844            for i in 0..chunks {
845                let chunk_size = if i < cnt_additional {
846                    chunk_size + 1
847                } else {
848                    chunk_size
849                };
850                let tensor = self.narrow(dim, sum_chunk_size, chunk_size)?;
851                tensors.push(tensor);
852                sum_chunk_size += chunk_size
853            }
854            Ok(tensors)
855        }
856    }
857
858    /// Returns a new tensor that is a narrowed version of the input, the dimension `dim`
859    /// ranges from `start` to `start + len`.
860    /// ```
861    /// use hanzo_ml::{Tensor, Device};
862    /// let a = Tensor::new(&[
863    ///     [0f32, 1., 2.],
864    ///     [3.  , 4., 5.],
865    ///     [6.  , 7., 8.]
866    /// ], &Device::Cpu)?;
867    ///
868    /// let b = a.narrow(0, 1, 2)?;
869    /// assert_eq!(b.shape().dims(), &[2, 3]);
870    /// assert_eq!(b.to_vec2::<f32>()?, &[
871    ///     [3., 4., 5.],
872    ///     [6., 7., 8.]
873    /// ]);
874    ///
875    /// let c = a.narrow(1, 1, 1)?;
876    /// assert_eq!(c.shape().dims(), &[3, 1]);
877    /// assert_eq!(c.to_vec2::<f32>()?, &[
878    ///     [1.],
879    ///     [4.],
880    ///     [7.]
881    /// ]);
882    /// # Ok::<(), hanzo_ml::Error>(())
883    /// ```
884    pub fn narrow<D: Dim>(&self, dim: D, start: usize, len: usize) -> Result<Self> {
885        let dims = self.dims();
886        let dim = dim.to_index(self.shape(), "narrow")?;
887        let err = |msg| {
888            Err::<(), _>(
889                Error::NarrowInvalidArgs {
890                    shape: self.shape().clone(),
891                    dim,
892                    start,
893                    len,
894                    msg,
895                }
896                .bt(),
897            )
898        };
899        if start > dims[dim] {
900            err("start > dim_len")?
901        }
902        if start.saturating_add(len) > dims[dim] {
903            err("start + len > dim_len")?
904        }
905        if start == 0 && dims[dim] == len {
906            Ok(self.clone())
907        } else {
908            let op = BackpropOp::new1(self, |t| Op::Narrow(t, dim, start, len));
909            let layout = self.layout().narrow(dim, start, len)?;
910            let tensor_ = Tensor_ {
911                id: TensorId::new(),
912                storage: self.storage.clone(),
913                layout,
914                op,
915                is_variable: false,
916                dtype: self.dtype,
917                device: self.device.clone(),
918            };
919            Ok(Tensor(Arc::new(tensor_)))
920        }
921    }
922
923    fn squeeze_dims(self, dims: &[usize]) -> Result<Self> {
924        match dims {
925            [] => Ok(self),
926            [i] => self.squeeze(*i),
927            dims => {
928                let dims = self
929                    .dims()
930                    .iter()
931                    .enumerate()
932                    .filter_map(|(dim_idx, &v)| {
933                        if dims.contains(&dim_idx) {
934                            None
935                        } else {
936                            Some(v)
937                        }
938                    })
939                    .collect::<Vec<_>>();
940                self.reshape(dims)
941            }
942        }
943    }
944
945    fn reduce_impl<D: Dim>(&self, dim: D, keepdim: bool, op: ReduceOp) -> Result<Self> {
946        let dim = dim.to_index(self.shape(), op.name())?;
947        let storage = self.storage().reduce_op(op, self.layout(), &[dim])?;
948        let mut dims = self.dims().to_vec();
949        dims[dim] = 1;
950        let op = match op {
951            ReduceOp::Sum | ReduceOp::Min | ReduceOp::Max => {
952                BackpropOp::new1(self, |arg| Op::Reduce(arg, op, dims.to_vec()))
953            }
954            ReduceOp::ArgMin | ReduceOp::ArgMax => BackpropOp::none(),
955        };
956        let res = from_storage(storage, dims, op, false);
957        if keepdim {
958            Ok(res)
959        } else {
960            res.squeeze_dims(&[dim])
961        }
962    }
963
964    fn sum_impl<D: Dims>(&self, sum_dims: D, keepdim: bool) -> Result<Self> {
965        let sum_dims = sum_dims.to_indexes(self.shape(), "sum")?;
966        let storage = self
967            .storage()
968            .reduce_op(ReduceOp::Sum, self.layout(), &sum_dims)?;
969        let mut dims = self.dims().to_vec();
970        for &sum_dim in sum_dims.iter() {
971            dims[sum_dim] = 1
972        }
973        let op = BackpropOp::new1(self, |a| Op::Reduce(a, ReduceOp::Sum, dims.to_vec()));
974        let sum = from_storage(storage, dims, op, false);
975        if keepdim {
976            Ok(sum)
977        } else {
978            sum.squeeze_dims(&sum_dims)
979        }
980    }
981
982    /// Roll the tensor input along the given dimension.
983    /// Elements that are shifted beyond the last position are re-introduced at the first position.
984    ///
985    /// ```rust
986    /// # use hanzo_ml::{Tensor, Device};
987    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
988    /// let tensor = tensor.roll(1, 0)?;
989    /// assert_eq!(tensor.to_vec2::<f32>()?, &[[4., 5.], [0., 1.], [2., 3.]]);
990    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
991    /// let tensor = tensor.roll(-1, 0)?;
992    /// assert_eq!(tensor.to_vec2::<f32>()?, &[[2., 3.], [4., 5.], [0., 1.]]);
993    /// # Ok::<(), hanzo_ml::Error>(())
994    /// ```
995    pub fn roll<D>(&self, shift: i32, dim: D) -> Result<Self>
996    where
997        D: Dim + Clone,
998    {
999        let dim = dim.to_index(self.shape(), "roll")?;
1000        let dim_size = self.dim(dim)?;
1001        let shift = shift.rem_euclid(dim_size as i32) as usize;
1002        if shift == 0 {
1003            Ok(self.clone())
1004        } else {
1005            let a = self.narrow(dim, 0, dim_size - shift)?;
1006            let b = self.narrow(dim, dim_size - shift, shift)?;
1007            Tensor::cat(&[&b, &a], dim)
1008        }
1009    }
1010
1011    /// Returns the sum of all elements in the input tensor. The sum is performed over all the
1012    /// input dimensions.
1013    ///
1014    /// The resulting tensor has a shape that is similar to the shape of the input tensor, except
1015    /// that the number of elements for each dimension index in `sum_dims` is 1.
1016    ///
1017    /// ```rust
1018    /// use hanzo_ml::{Tensor, Device};
1019    /// let a = Tensor::new(&[[0f32, 1.], [2., 3.]], &Device::Cpu)?;
1020    /// let s = a.sum_keepdim(0)?;
1021    /// assert_eq!(s.to_vec2::<f32>()?, &[[2., 4.]]);
1022    /// let s = a.sum_keepdim(1)?;
1023    /// assert_eq!(s.to_vec2::<f32>()?, &[[1.], [5.]]);
1024    /// let s = a.sum_keepdim((0, 1))?;
1025    /// assert_eq!(s.to_vec2::<f32>()?, &[[6.]]);
1026    /// # Ok::<(), hanzo_ml::Error>(())
1027    /// ```
1028    pub fn sum_keepdim<D: Dims>(&self, sum_dims: D) -> Result<Self> {
1029        self.sum_impl(sum_dims, true)
1030    }
1031
1032    /// Returns the sum of all elements in the input tensor. The sum is performed over all the
1033    /// input dimensions and compared to `sum_keepdim` these dimensions are squeezed rather than
1034    /// kept.
1035    pub fn sum<D: Dims>(&self, sum_dims: D) -> Result<Self> {
1036        self.sum_impl(sum_dims, false)
1037    }
1038
1039    /// Returns the mean of all elements in the input tensor. The mean is performed over all the
1040    /// input dimensions.
1041    ///
1042    /// The resulting tensor has a shape that is similar to the shape of the input tensor, except
1043    /// that the number of elements for each dimension index in `mean_dims` is 1.
1044    ///
1045    /// ```rust
1046    /// use hanzo_ml::{Tensor, Device};
1047    /// let a = Tensor::new(&[[0f32, 1.], [2., 3.]], &Device::Cpu)?;
1048    /// let s = a.mean_keepdim(0)?;
1049    /// assert_eq!(s.to_vec2::<f32>()?, &[[1., 2.]]);
1050    /// let s = a.mean_keepdim(1)?;
1051    /// assert_eq!(s.to_vec2::<f32>()?, &[[0.5], [2.5]]);
1052    /// let s = a.mean_keepdim((0, 1))?;
1053    /// assert_eq!(s.to_vec2::<f32>()?, &[[1.5]]);
1054    /// # Ok::<(), hanzo_ml::Error>(())
1055    /// ```
1056    pub fn mean_keepdim<D: Dims>(&self, mean_dims: D) -> Result<Self> {
1057        let mean_dims = mean_dims.to_indexes(self.shape(), "mean-keepdim")?;
1058        let reduced_dim: usize = mean_dims.iter().map(|i| self.dims()[*i]).product();
1059        let scale = 1f64 / (reduced_dim as f64);
1060        self.sum_impl(mean_dims, true)? * scale
1061    }
1062
1063    /// Returns the mean of all elements in the input tensor. The mean is performed over all the
1064    /// input dimensions and compared to `mean_keepdim` these dimensions are squeezed rather than
1065    /// kept.
1066    pub fn mean<D: Dims>(&self, mean_dims: D) -> Result<Self> {
1067        let mean_dims = mean_dims.to_indexes(self.shape(), "mean")?;
1068        let reduced_dim: usize = mean_dims.iter().map(|i| self.dims()[*i]).product();
1069        let scale = 1f64 / (reduced_dim as f64);
1070        self.sum_impl(mean_dims, false)? * scale
1071    }
1072
1073    /// Returns the unbiased variance over the selected dimension.
1074    pub fn var_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1075        let dim = dim.to_index(self.shape(), "var")?;
1076        let mean = self.mean_keepdim(dim)?;
1077        let squares = self.broadcast_sub(&mean)?.sqr()?;
1078        squares.sum_impl(dim, true)? / (self.dim(dim)? - 1) as f64
1079    }
1080
1081    /// Returns the unbiased variance over the selected dimension.
1082    pub fn var<D: Dim>(&self, dim: D) -> Result<Self> {
1083        let dim = dim.to_index(self.shape(), "var")?;
1084        self.var_keepdim(dim)?.squeeze(dim)
1085    }
1086
1087    /// Gathers the maximum value across the selected dimension. The resulting shape has the same
1088    /// number of dimensions as the original tensor and the select dimension has a single element.
1089    pub fn max_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1090        self.reduce_impl(dim, true, ReduceOp::Max)
1091    }
1092
1093    /// Similar to `max_keepdim` but the target dimension is squeezed.
1094    pub fn max<D: Dim>(&self, dim: D) -> Result<Self> {
1095        self.reduce_impl(dim, false, ReduceOp::Max)
1096    }
1097
1098    /// Gathers the minimum value across the selected dimension. The resulting shape has the same
1099    /// number of dimensions as the original tensor and the select dimension has a single element.
1100    pub fn min_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1101        self.reduce_impl(dim, true, ReduceOp::Min)
1102    }
1103
1104    /// Similar to `min_keepdim` but the target dimension is squeezed.
1105    pub fn min<D: Dim>(&self, dim: D) -> Result<Self> {
1106        self.reduce_impl(dim, false, ReduceOp::Min)
1107    }
1108
1109    pub fn argmax_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1110        self.reduce_impl(dim, true, ReduceOp::ArgMax)
1111    }
1112
1113    /// Similar to `argmax_keepdim` but the target dimension is squeezed.
1114    pub fn argmax<D: Dim>(&self, dim: D) -> Result<Self> {
1115        self.reduce_impl(dim, false, ReduceOp::ArgMax)
1116    }
1117
1118    pub fn argmin_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1119        self.reduce_impl(dim, true, ReduceOp::ArgMin)
1120    }
1121
1122    /// Similar to `argmin_keepdim` but the target dimension is squeezed.
1123    pub fn argmin<D: Dim>(&self, dim: D) -> Result<Self> {
1124        self.reduce_impl(dim, false, ReduceOp::ArgMin)
1125    }
1126
1127    /// Element-wise comparison between two tensors, e.g. equality, greater than, ... The actual
1128    /// comparison operation is specified by the `op` argument.
1129    ///
1130    /// The returned tensor has the same shape as the original tensors and uses `u8` elements.
1131    pub fn cmp<T: TensorOrScalar>(&self, rhs: T, op: CmpOp) -> Result<Self> {
1132        let rhs = match rhs.to_tensor_scalar()? {
1133            crate::scalar::TensorScalar::Tensor(rhs) => rhs,
1134            crate::scalar::TensorScalar::Scalar(rhs) => rhs
1135                .to_dtype(self.dtype())?
1136                .to_device(self.device())?
1137                .broadcast_as(self.shape())?,
1138        };
1139        let shape = self.same_shape_binary_op(&rhs, "cmp")?;
1140        let storage = self
1141            .storage()
1142            .cmp(op, &rhs.storage(), self.layout(), rhs.layout())?;
1143        let op = BackpropOp::new1(self, |a| Op::Cmp(a, op));
1144        Ok(from_storage(storage, shape.dims(), op, false))
1145    }
1146
1147    /// Element-wise equality.
1148    pub fn eq<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1149        self.cmp(rhs, CmpOp::Eq)
1150    }
1151
1152    /// Element-wise non-equality.
1153    pub fn ne<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1154        self.cmp(rhs, CmpOp::Ne)
1155    }
1156
1157    /// Element-wise comparison with lower-than, the returned tensor uses value 1 where `self <
1158    /// rhs` and 0 otherwise.
1159    pub fn lt<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1160        self.cmp(rhs, CmpOp::Lt)
1161    }
1162
1163    /// Element-wise comparison with greater-than, the returned tensor uses value 1 where `self >
1164    /// rhs` and 0 otherwise.
1165    pub fn gt<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1166        self.cmp(rhs, CmpOp::Gt)
1167    }
1168
1169    /// Element-wise comparison with greater-equal, the returned tensor uses value 1 where `self >=
1170    /// rhs` and 0 otherwise.
1171    pub fn ge<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1172        self.cmp(rhs, CmpOp::Ge)
1173    }
1174
1175    /// Element-wise comparison with lower-equal, the returned tensor uses value 1 where `self <=
1176    /// rhs` and 0 otherwise.
1177    pub fn le<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1178        self.cmp(rhs, CmpOp::Le)
1179    }
1180
1181    /// Clamp the tensor values to be between `min` and `max`.
1182    pub fn clamp<T1: TensorOrScalar, T2: TensorOrScalar>(&self, min: T1, max: T2) -> Result<Self> {
1183        self.maximum(min)?.minimum(max)
1184    }
1185
1186    /// Interpolate the input tensor to the `target_size` size, taking the value of the nearest element.
1187    ///
1188    /// The input tensor should have three dimensions, `(batch, channels, l)`, the returned
1189    /// tensor also has three dimensions, `(batch, channels, target_size)`.
1190    pub fn interpolate1d(&self, target_size: usize) -> Result<Self> {
1191        let (n, c, _l) = self.dims3()?;
1192        let op = BackpropOp::new1(self, |arg| Op::UpsampleNearest1D { arg, target_size });
1193        let storage = self
1194            .storage()
1195            .upsample_nearest1d(self.layout(), target_size)?;
1196        Ok(from_storage(storage, (n, c, target_size), op, false))
1197    }
1198
1199    /// Alias for `interpolate1d`.
1200    pub fn upsample_nearest1d(&self, target_size: usize) -> Result<Self> {
1201        self.interpolate1d(target_size)
1202    }
1203
1204    /// Interpolate the input tensor to the `(target_h, target_w)` size, taking the value of the
1205    /// nearest element.
1206    ///
1207    /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned
1208    /// tensor also has four dimensions, `(batch, channels, target_h, target_w)`.
1209    pub fn interpolate2d(&self, target_h: usize, target_w: usize) -> Result<Self> {
1210        let (n, c, _h, _w) = self.dims4()?;
1211        let op = BackpropOp::new1(self, |arg| Op::UpsampleNearest2D {
1212            arg,
1213            target_h,
1214            target_w,
1215        });
1216        let storage = self
1217            .storage()
1218            .upsample_nearest2d(self.layout(), target_h, target_w)?;
1219        Ok(from_storage(storage, (n, c, target_h, target_w), op, false))
1220    }
1221
1222    /// Alias for `interpolate2d`.
1223    pub fn upsample_nearest2d(&self, target_h: usize, target_w: usize) -> Result<Self> {
1224        self.interpolate2d(target_h, target_w)
1225    }
1226
1227    /// Bilinear interpolation to resize the input tensor to the specified size.
1228    ///
1229    /// The input tensor should have four dimensions: `(batch, channels, h, w)`.
1230    /// The returned tensor also has four dimensions: `(batch, channels, target_h, target_w)`.
1231    ///
1232    /// # Arguments
1233    ///
1234    /// * `target_h` - Target height
1235    /// * `target_w` - Target width  
1236    /// * `align_corners` - If true, corner pixels are aligned. If false (default),
1237    ///   pixels are treated as areas (matches PyTorch default behavior).
1238    ///
1239    /// # Example
1240    ///
1241    /// ```rust
1242    /// use hanzo_ml::{Tensor, Device};
1243    /// # fn main() -> hanzo_ml::Result<()> {
1244    /// let t = Tensor::arange(0f32, 16f32, &Device::Cpu)?.reshape((1, 1, 4, 4))?;
1245    /// let upsampled = t.upsample_bilinear2d(8, 8, false)?;
1246    /// assert_eq!(upsampled.dims(), &[1, 1, 8, 8]);
1247    /// # Ok(())
1248    /// # }
1249    /// ```
1250    pub fn upsample_bilinear2d(
1251        &self,
1252        target_h: usize,
1253        target_w: usize,
1254        align_corners: bool,
1255    ) -> Result<Self> {
1256        let (n, c, _h, _w) = self.dims4()?;
1257        let op = BackpropOp::new1(self, |arg| Op::UpsampleBilinear2D {
1258            arg,
1259            target_h,
1260            target_w,
1261            align_corners,
1262        });
1263        // Pass None for scale factors (size mode)
1264        let storage = self.storage().upsample_bilinear2d(
1265            self.layout(),
1266            target_h,
1267            target_w,
1268            align_corners,
1269            None,
1270            None,
1271        )?;
1272        Ok(from_storage(storage, (n, c, target_h, target_w), op, false))
1273    }
1274
1275    /// Bilinear interpolation using scale factors.
1276    ///
1277    /// Similar to `upsample_bilinear2d` but uses scale factors instead of absolute sizes.
1278    /// This matches PyTorch's `interpolate(scale_factor=...)` behavior.
1279    ///
1280    /// # Arguments
1281    ///
1282    /// * `scale_h` - Height scaling factor
1283    /// * `scale_w` - Width scaling factor
1284    /// * `align_corners` - If true, corner pixels are aligned
1285    ///
1286    /// # Example
1287    ///
1288    /// ```rust
1289    /// use hanzo_ml::{Tensor, Device};
1290    /// # fn main() -> hanzo_ml::Result<()> {
1291    /// let t = Tensor::arange(0f32, 16f32, &Device::Cpu)?.reshape((1, 1, 4, 4))?;
1292    /// // Scale by 2x in both dimensions
1293    /// let upsampled = t.upsample_bilinear2d_with_scale(2.0, 2.0, false)?;
1294    /// assert_eq!(upsampled.dims(), &[1, 1, 8, 8]);
1295    /// # Ok(())
1296    /// # }
1297    /// ```
1298    pub fn upsample_bilinear2d_with_scale(
1299        &self,
1300        scale_h: f64,
1301        scale_w: f64,
1302        align_corners: bool,
1303    ) -> Result<Self> {
1304        let (n, c, height_in, width_in) = self.dims4()?;
1305
1306        // Calculate output size (floor, matching PyTorch)
1307        let height_out = (height_in as f64 * scale_h).floor() as usize;
1308        let width_out = (width_in as f64 * scale_w).floor() as usize;
1309
1310        // Early return if size unchanged
1311        if height_in == height_out && width_in == width_out {
1312            return Ok(self.clone());
1313        }
1314
1315        let op = BackpropOp::new1(self, |arg| Op::UpsampleBilinear2D {
1316            arg,
1317            target_h: height_out,
1318            target_w: width_out,
1319            align_corners,
1320        });
1321
1322        // Pass original scale factors (scale_factor mode)
1323        // This ensures PyTorch-compatible scale calculation
1324        let storage = self.storage().upsample_bilinear2d(
1325            self.layout(),
1326            height_out,
1327            width_out,
1328            align_corners,
1329            Some(scale_h),
1330            Some(scale_w),
1331        )?;
1332        Ok(from_storage(
1333            storage,
1334            (n, c, height_out, width_out),
1335            op,
1336            false,
1337        ))
1338    }
1339
1340    /// 2D average pooling over an input tensor with multiple channels.
1341    ///
1342    /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned
1343    /// tensor also has four dimensions, `(batch, channels, h', w')`. The pooling is performed on
1344    /// the two last dimensions using a kernel of size `sz`. The returned element is the average
1345    /// value over the kernel window.
1346    pub fn avg_pool2d<T: crate::ToUsize2>(&self, sz: T) -> Result<Self> {
1347        let sz = sz.to_usize2();
1348        self.avg_pool2d_with_stride(sz, sz)
1349    }
1350
1351    /// Same as `avg_pool2d` but with a `stride` that can be set to a value different from the
1352    /// kernel size.
1353    pub fn avg_pool2d_with_stride<T: crate::ToUsize2>(
1354        &self,
1355        kernel_size: T,
1356        stride: T,
1357    ) -> Result<Self> {
1358        let kernel_size = kernel_size.to_usize2();
1359        let stride = stride.to_usize2();
1360        let (n, c, h, w) = self.dims4()?;
1361        if h < kernel_size.0 || w < kernel_size.1 {
1362            bail!("kernel-size {kernel_size:?} is larger than the input size {h},{w}")
1363        }
1364        // https://pytorch.org/docs/stable/generated/torch.nn.AvgPool2d.html#torch.nn.AvgPool2d
1365        let h_out = (h - kernel_size.0) / stride.0 + 1;
1366        let w_out = (w - kernel_size.1) / stride.1 + 1;
1367        let op = BackpropOp::new1(self, |arg| Op::AvgPool2D {
1368            arg,
1369            kernel_size,
1370            stride,
1371        });
1372        let storage = self
1373            .storage()
1374            .avg_pool2d(self.layout(), kernel_size, stride)?;
1375        Ok(from_storage(storage, (n, c, h_out, w_out), op, false))
1376    }
1377
1378    /// 2D max pooling over an input tensor with multiple channels.
1379    ///
1380    /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned
1381    /// tensor also has four dimensions, `(batch, channels, h', w')`. The pooling is performed on
1382    /// the two last dimensions using a kernel of size `sz`, the returned element is the maximum
1383    /// value over the kernel window.
1384    pub fn max_pool2d<T: crate::ToUsize2>(&self, sz: T) -> Result<Self> {
1385        let sz = sz.to_usize2();
1386        self.max_pool2d_with_stride(sz, sz)
1387    }
1388
1389    /// Same as `max_pool2d` but with a `stride` that can be set to a value different from the
1390    /// kernel size.
1391    pub fn max_pool2d_with_stride<T: crate::ToUsize2>(
1392        &self,
1393        kernel_size: T,
1394        stride: T,
1395    ) -> Result<Self> {
1396        let kernel_size = kernel_size.to_usize2();
1397        let stride = stride.to_usize2();
1398        let (n, c, h, w) = self.dims4()?;
1399        if h < kernel_size.0 || w < kernel_size.1 {
1400            bail!("kernel-size {kernel_size:?} is larger than the input size {h},{w}")
1401        }
1402        // https://pytorch.org/docs/stable/generated/torch.nn.MaxPool2d.html#torch.nn.MaxPool2d
1403        let h_out = (h - kernel_size.0) / stride.0 + 1;
1404        let w_out = (w - kernel_size.1) / stride.1 + 1;
1405        let op = BackpropOp::new1(self, |arg| Op::MaxPool2D {
1406            arg,
1407            kernel_size,
1408            stride,
1409        });
1410        let storage = self
1411            .storage()
1412            .max_pool2d(self.layout(), kernel_size, stride)?;
1413        Ok(from_storage(storage, (n, c, h_out, w_out), op, false))
1414    }
1415
1416    /// Computes the dot product of two 1D tensors.
1417    ///
1418    /// - If inputs are 1D vectors (`[n]`), returns their scalar dot product.
1419    /// - Panics if shapes are not compatible
1420    /// - Not supported for integer dtypes
1421    ///
1422    /// # Example (vectors)
1423    /// ```rust
1424    /// use hanzo_ml::{Tensor, Device};
1425    /// let t1 = Tensor::new(&[1.0, 2.0, 3.0], &Device::Cpu)?;
1426    /// let t2 = Tensor::new(&[4.0, 5.0, 6.0], &Device::Cpu)?;
1427    /// let res = t1.dot(&t2)?;
1428    /// assert_eq!(res.to_scalar::<f64>()?, 32.);
1429    /// # Ok::<(), hanzo_ml::Error>(())
1430    /// ```
1431    pub fn dot(&self, rhs: &Self) -> Result<Self> {
1432        if self.dims().len() != 1 || rhs.dims().len() != 1 {
1433            return Err(Error::ShapeMismatchBinaryOp {
1434                lhs: self.shape().clone(),
1435                rhs: rhs.shape().clone(),
1436                op: "dot",
1437            });
1438        }
1439
1440        (self * rhs).and_then(|ret| ret.sum_all())
1441    }
1442
1443    /// Computes the **Frobenius norm** (L2 norm of all elements) of the tensor.
1444    /// - Output is `sqrt(sum(x^2))`.
1445    /// - Always returns a scalar (`[]` shape).
1446    ///
1447    /// # Example
1448    /// ```rust
1449    /// use hanzo_ml::{Tensor, Device};
1450    /// let t = Tensor::new(&[[3., 4.], [0., 0.]], &Device::Cpu)?;
1451    /// let norm = t.norm()?;
1452    /// assert_eq!(norm.to_scalar::<f64>()?, 5.);
1453    /// # Ok::<(), hanzo_ml::Error>(())
1454    /// ```
1455    pub fn norm(&self) -> Result<Self> {
1456        if self.dtype().is_int() {
1457            bail!("norm not supported for integer dtypes");
1458        }
1459
1460        self.sqr().and_then(|x| x.sum_all()).and_then(|x| x.sqrt())
1461    }
1462
1463    /// Performs strict matrix-vector multiplication (`[m, n] * [n] = [m]`).
1464    ///
1465    /// - If `self` is a matrix (`[m, n]`) and `rhs` is a vector (`[n]`), returns a vector (`[m]`).
1466    /// - **No broadcasting**: Panics if `self` is not 2D or if `rhs` is not 1D with matching size.
1467    ///
1468    /// # Example
1469    /// ```rust
1470    /// use hanzo_ml::{Tensor, Device};
1471    /// let mat = Tensor::new(&[[1., 2., 3.], [4., 5., 6.]], &Device::Cpu)?;
1472    /// let vec = Tensor::new(&[1., 1., 1.], &Device::Cpu)?;
1473    /// let res = mat.mv(&vec)?;
1474    /// assert_eq!(res.to_vec1::<f64>()?, [6., 15.]);
1475    /// # Ok::<(), hanzo_ml::Error>(())
1476    /// ```
1477    pub fn mv(&self, rhs: &Self) -> Result<Self> {
1478        // Strict shape checks
1479        let lhs_dims = self.dims();
1480        let rhs_dims = rhs.dims();
1481        if lhs_dims.len() != 2 || rhs_dims.len() != 1 || lhs_dims[1] != rhs_dims[0] {
1482            return Err(Error::ShapeMismatchBinaryOp {
1483                lhs: self.shape().clone(),
1484                rhs: rhs.shape().clone(),
1485                op: "mv",
1486            });
1487        }
1488
1489        // Direct matmul after ensuring rhs is column vector
1490        self.matmul(&rhs.unsqueeze(1)?)?.squeeze(1)
1491    }
1492
1493    /// Returns the matrix-multiplication of the input tensor with the other provided tensor.
1494    ///
1495    /// # Arguments
1496    ///
1497    /// * `self` - A tensor with dimensions `b1, b2, ..., bi, m, k`.
1498    /// * `rhs` - A tensor with dimensions `b1, b2, ..., bi, k, n`.
1499    ///
1500    /// The resulting tensor has dimensions `b1, b2, ..., bi, m, n`.
1501    pub fn matmul(&self, rhs: &Self) -> Result<Self> {
1502        let a_dims = self.shape().dims();
1503        let b_dims = rhs.shape().dims();
1504
1505        let dim = a_dims.len();
1506
1507        if dim < 2 || b_dims.len() != dim {
1508            Err(Error::ShapeMismatchBinaryOp {
1509                lhs: self.shape().clone(),
1510                rhs: rhs.shape().clone(),
1511                op: "matmul",
1512            }
1513            .bt())?
1514        }
1515
1516        let m = a_dims[dim - 2];
1517        let k = a_dims[dim - 1];
1518        let k2 = b_dims[dim - 2];
1519        let n = b_dims[dim - 1];
1520
1521        let c_shape = Shape::from(&a_dims[..dim - 2]).extend(&[m, n]);
1522        if c_shape.elem_count() == 0 || k == 0 {
1523            return Tensor::zeros(c_shape, self.dtype(), self.device());
1524        }
1525        let batching: usize = a_dims[..dim - 2].iter().product();
1526        let batching_b: usize = b_dims[..dim - 2].iter().product();
1527        if k != k2 || batching != batching_b {
1528            Err(Error::ShapeMismatchBinaryOp {
1529                lhs: self.shape().clone(),
1530                rhs: rhs.shape().clone(),
1531                op: "matmul",
1532            }
1533            .bt())?
1534        }
1535
1536        let storage = self.storage().matmul(
1537            &rhs.storage(),
1538            (batching, m, n, k),
1539            self.layout(),
1540            rhs.layout(),
1541        )?;
1542        let op = BackpropOp::new2(self, rhs, Op::Matmul);
1543        Ok(from_storage(storage, c_shape, op, false))
1544    }
1545
1546    /// Matrix-multiplication with broadcasting support.
1547    ///
1548    /// Compared to `matmul` the two matrixes are allowed to have different dimensions as long as
1549    /// they are compatible for broadcast. E.g. if `self` has shape `(j, 1, n, k)` and `rhs` has
1550    /// shape `(l, k, m)`, the output will have shape `(j, l, n, m)`.
1551    pub fn broadcast_matmul(&self, rhs: &Self) -> Result<Self> {
1552        let lhs = self;
1553        let (l_shape, r_shape) = lhs.shape().broadcast_shape_matmul(rhs.shape())?;
1554        let l_broadcast = l_shape != *lhs.shape();
1555        let r_broadcast = r_shape != *rhs.shape();
1556        // TODO: Avoid concretising the broadcasted matrixes via contiguous.
1557        match (l_broadcast, r_broadcast) {
1558            (true, true) => lhs
1559                .broadcast_as(&l_shape)?
1560                .contiguous()?
1561                .matmul(&rhs.broadcast_as(&r_shape)?.contiguous()?),
1562            (false, true) => lhs.matmul(&rhs.broadcast_as(&r_shape)?.contiguous()?),
1563            (true, false) => lhs.broadcast_as(&l_shape)?.contiguous()?.matmul(rhs),
1564            (false, false) => lhs.matmul(rhs),
1565        }
1566    }
1567
1568    /// Returns a tensor with the same shape as the input tensor, the values are taken from
1569    /// `on_true` if the input tensor value is not zero, and `on_false` at the positions where the
1570    /// input tensor is equal to zero.
1571    pub fn where_cond(&self, on_true: &Self, on_false: &Self) -> Result<Self> {
1572        let _shap = self.same_shape_binary_op(on_true, "where_cond")?;
1573        let shape = self.same_shape_binary_op(on_false, "where_cond")?;
1574        let storage = self.storage().where_cond(
1575            self.layout(),
1576            &on_true.storage(),
1577            on_true.layout(),
1578            &on_false.storage(),
1579            on_false.layout(),
1580        )?;
1581        let op = BackpropOp::new3(self, on_true, on_false, Op::WhereCond);
1582        Ok(from_storage(storage, shape, op, false))
1583    }
1584
1585    /// Returns a tensor with the values from the `self` tensor at the index corresponding to the
1586    /// values hold in the `ids` tensor.
1587    ///
1588    /// # Arguments
1589    ///
1590    /// * `self` - A tensor with dimensions `v, h`.
1591    /// * `ids` - A tensor with dimensions `s` and with integer values between 0 and v (exclusive).
1592    ///
1593    /// The resulting tensor has dimensions `s, h`. `s` is called the sequence length, `v` the
1594    /// vocabulary size, and `h` the hidden size.
1595    ///
1596    /// ```rust
1597    /// use hanzo_ml::{Tensor, Device};
1598    /// let values = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
1599    /// let ids = Tensor::new(&[2u32, 1u32, 2u32], &Device::Cpu)?;
1600    /// let emb = values.embedding(&ids)?;
1601    /// assert_eq!(emb.to_vec2::<f32>()?, &[[4., 5.], [2., 3.], [4., 5.]]);
1602    /// # Ok::<(), hanzo_ml::Error>(())
1603    /// ```
1604    pub fn embedding(&self, ids: &Self) -> Result<Self> {
1605        if self.rank() != 2 || ids.rank() != 1 {
1606            Err(Error::ShapeMismatchBinaryOp {
1607                lhs: self.shape().clone(),
1608                rhs: ids.shape().clone(),
1609                op: "embedding",
1610            }
1611            .bt())?
1612        }
1613        self.index_select(ids, 0)
1614    }
1615
1616    fn scatter_checks(&self, indexes: &Self, source: &Self, dim: usize) -> Result<()> {
1617        let source_dims = source.dims();
1618        let self_dims = self.dims();
1619        let mismatch = if source_dims.len() != self_dims.len() {
1620            true
1621        } else {
1622            let mut mismatch = false;
1623            for (i, (&d1, &d2)) in self_dims.iter().zip(source_dims.iter()).enumerate() {
1624                if i != dim && d1 != d2 {
1625                    mismatch = true;
1626                    break;
1627                }
1628            }
1629            mismatch
1630        };
1631        if mismatch {
1632            Err(Error::ShapeMismatchBinaryOp {
1633                op: "scatter (self, src)",
1634                lhs: self.shape().clone(),
1635                rhs: source.shape().clone(),
1636            }
1637            .bt())?
1638        }
1639        if indexes.dims() != source.dims() {
1640            Err(Error::ShapeMismatchBinaryOp {
1641                op: "scatter (indexes, src)",
1642                lhs: indexes.shape().clone(),
1643                rhs: source.shape().clone(),
1644            }
1645            .bt())?
1646        }
1647        Ok(())
1648    }
1649
1650    pub fn scatter<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<Self> {
1651        let dim = dim.to_index(self.shape(), "scatter")?;
1652        self.scatter_checks(indexes, source, dim)?;
1653        let shape = self.shape();
1654        let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
1655        self.storage()
1656            .copy_strided_src(&mut storage, 0, self.layout())?;
1657        let layout = Layout::contiguous(shape);
1658        storage.scatter_set(
1659            &layout,
1660            &indexes.storage(),
1661            indexes.layout(),
1662            &source.storage(),
1663            source.layout(),
1664            dim,
1665        )?;
1666        let op = BackpropOp::new3(self, indexes, source, |t1, t2, t3| {
1667            Op::Scatter(t1, t2, t3, dim)
1668        });
1669        Ok(from_storage(storage, self.shape(), op, false))
1670    }
1671
1672    pub fn scatter_set<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<()> {
1673        if self.same_storage(source) {
1674            crate::bail!("cannot use slice_set when self and src share their storage")
1675        }
1676        let dim = dim.to_index(self.shape(), "scatter-set")?;
1677        self.scatter_checks(indexes, source, dim)?;
1678        self.storage_mut().scatter_set(
1679            self.layout(),
1680            &indexes.storage(),
1681            indexes.layout(),
1682            &source.storage(),
1683            source.layout(),
1684            dim,
1685        )?;
1686        Ok(())
1687    }
1688
1689    pub fn scatter_add<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<Self> {
1690        let dim = dim.to_index(self.shape(), "scatter-add")?;
1691        self.scatter_checks(indexes, source, dim)?;
1692        let shape = self.shape();
1693        let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
1694        self.storage()
1695            .copy_strided_src(&mut storage, 0, self.layout())?;
1696        let layout = Layout::contiguous(shape);
1697        storage.scatter_add(
1698            &layout,
1699            &indexes.storage(),
1700            indexes.layout(),
1701            &source.storage(),
1702            source.layout(),
1703            dim,
1704        )?;
1705        let op = BackpropOp::new3(self, indexes, source, |t1, t2, t3| {
1706            Op::ScatterAdd(t1, t2, t3, dim)
1707        });
1708        Ok(from_storage(storage, self.shape(), op, false))
1709    }
1710
1711    pub fn scatter_add_set<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<()> {
1712        if self.same_storage(source) {
1713            crate::bail!("cannot use slice_set when self and src share their storage")
1714        }
1715        let dim = dim.to_index(self.shape(), "scatter-add-set")?;
1716        self.scatter_checks(indexes, source, dim)?;
1717        self.storage_mut().scatter_add(
1718            self.layout(),
1719            &indexes.storage(),
1720            indexes.layout(),
1721            &source.storage(),
1722            source.layout(),
1723            dim,
1724        )?;
1725        Ok(())
1726    }
1727
1728    /// Embeds the values of the `src` tensor into the `self` tensor on the specified dimension.
1729    pub fn slice_scatter<D: Dim>(&self, src: &Self, dim: D, start: usize) -> Result<Self> {
1730        let dim = dim.to_index(self.shape(), "slice-scatter")?;
1731        if dim == 0 {
1732            self.slice_scatter0(src, start)
1733        } else {
1734            // TODO: Maybe we want to add a more efficient implementation at some point.
1735            self.transpose(0, dim)?
1736                .slice_scatter0(&src.transpose(0, dim)?, start)?
1737                .transpose(0, dim)
1738        }
1739    }
1740
1741    /// Embeds the values of the `src` tensor into the `self` tensor on the first dimension.
1742    pub fn slice_scatter0(&self, src: &Self, start: usize) -> Result<Self> {
1743        if self.dtype() != src.dtype() {
1744            Err(Error::DTypeMismatchBinaryOp {
1745                lhs: self.dtype(),
1746                rhs: src.dtype(),
1747                op: "slice-scatter",
1748            }
1749            .bt())?
1750        }
1751        if self.device().location() != src.device.location() {
1752            Err(Error::DeviceMismatchBinaryOp {
1753                lhs: self.device().location(),
1754                rhs: src.device().location(),
1755                op: "slice-scatter",
1756            }
1757            .bt())?
1758        }
1759        if self.rank() != src.rank() {
1760            Err(Error::UnexpectedNumberOfDims {
1761                expected: self.rank(),
1762                got: src.rank(),
1763                shape: src.shape().clone(),
1764            }
1765            .bt())?
1766        }
1767        let shape_ok =
1768            self.dims()
1769                .iter()
1770                .zip(src.dims().iter())
1771                .enumerate()
1772                .all(|(dim_idx, (&d1, &d2))| {
1773                    if 0 == dim_idx {
1774                        d2 + start <= d1
1775                    } else {
1776                        d1 == d2
1777                    }
1778                });
1779        if !shape_ok {
1780            Err(Error::ShapeMismatchBinaryOp {
1781                op: "slice-scatter (self, src)",
1782                lhs: self.shape().clone(),
1783                rhs: src.shape().clone(),
1784            }
1785            .bt())?
1786        }
1787        let mut storage = unsafe { self.device().alloc_uninit(self.shape(), self.dtype())? };
1788        self.storage()
1789            .copy_strided_src(&mut storage, 0, self.layout())?;
1790        let offset = start * src.dims()[1..].iter().product::<usize>();
1791        src.storage()
1792            .copy_strided_src(&mut storage, offset, src.layout())?;
1793        let op = BackpropOp::new2(self, src, |t1, t2| Op::SliceScatter0(t1, t2, start));
1794        Ok(from_storage(storage, self.shape(), op, false))
1795    }
1796
1797    /// Accumulate element from `source` at indexes `indexes` and add them to `self`.
1798    pub fn index_add<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<Self> {
1799        let dim = dim.to_index(self.shape(), "index-add")?;
1800        let source_dims = source.dims();
1801        let self_dims = self.dims();
1802        let mismatch = if source_dims.len() != self_dims.len() {
1803            true
1804        } else {
1805            let mut mismatch = false;
1806            for (i, (&d1, &d2)) in self_dims.iter().zip(source_dims.iter()).enumerate() {
1807                if i != dim && d1 != d2 {
1808                    mismatch = true;
1809                    break;
1810                }
1811            }
1812            mismatch
1813        };
1814        if mismatch {
1815            Err(Error::ShapeMismatchBinaryOp {
1816                op: "index-add (self, source)",
1817                lhs: self.shape().clone(),
1818                rhs: source.shape().clone(),
1819            }
1820            .bt())?
1821        }
1822        // The number of element in indexes must match the dimension on which the add is
1823        // performed on the source tensor (and the index values from `indexes` are taken from
1824        // the target tensor self)
1825        let indexes_len = indexes.dims1()?;
1826        if source_dims[dim] != indexes_len {
1827            Err(Error::ShapeMismatchBinaryOp {
1828                op: "index-add (ids, source))",
1829                lhs: indexes.shape().clone(),
1830                rhs: source.shape().clone(),
1831            }
1832            .bt())?
1833        }
1834        let storage = self.storage().index_add(
1835            self.layout(),
1836            &indexes.storage(),
1837            indexes.layout(),
1838            &source.storage(),
1839            source.layout(),
1840            dim,
1841        )?;
1842        let op = BackpropOp::new3(self, indexes, source, |t1, t2, t3| {
1843            Op::IndexAdd(t1, t2, t3, dim)
1844        });
1845        Ok(from_storage(storage, self.shape(), op, false))
1846    }
1847
1848    /// Gather values across the target dimension.
1849    ///
1850    /// # Arguments
1851    ///
1852    /// * `self` - The input tensor.
1853    /// * `indexes` - The indices of elements to gather, this should have same number of dimensions as `self`
1854    ///   and indexes.dims()[d] <= self.dims()[d] for all dimensions d != dim
1855    /// * `dim` - the target dimension.
1856    ///
1857    /// The resulting tensor has the same shape as `indexes` and use values from `self` indexed on
1858    /// dimension `dim` by the values in `indexes`.
1859    pub fn gather<D: Dim>(&self, indexes: &Self, dim: D) -> Result<Self> {
1860        let dim = dim.to_index(self.shape(), "gather")?;
1861
1862        let self_dims = self.dims();
1863        let indexes_dims = indexes.dims();
1864        let mismatch = if indexes_dims.len() != self_dims.len() {
1865            true
1866        } else {
1867            let mut mismatch = false;
1868            for (i, (&d1, &d2)) in self_dims.iter().zip(indexes_dims.iter()).enumerate() {
1869                if i != dim && d1 < d2 {
1870                    mismatch = true;
1871                    break;
1872                }
1873            }
1874            mismatch
1875        };
1876        if mismatch {
1877            Err(Error::ShapeMismatchBinaryOp {
1878                op: "gather",
1879                lhs: self.shape().clone(),
1880                rhs: indexes.shape().clone(),
1881            }
1882            .bt())?
1883        }
1884        let storage =
1885            self.storage()
1886                .gather(self.layout(), &indexes.storage(), indexes.layout(), dim)?;
1887        let op = BackpropOp::new2(self, indexes, |t1, t2| Op::Gather(t1, t2, dim));
1888        Ok(from_storage(storage, indexes.shape(), op, false))
1889    }
1890
1891    /// Select values for the input tensor at the target indexes across the specified dimension.
1892    ///
1893    /// The `indexes` is argument is an int tensor with a single dimension.
1894    /// The output has the same number of dimension as the `self` input. The target dimension of
1895    /// the output has length the length of `indexes` and the values are taken from `self` using
1896    /// the index from `indexes`. Other dimensions have the same number of elements as the input
1897    /// tensor.
1898    pub fn index_select<D: Dim>(&self, indexes: &Self, dim: D) -> Result<Self> {
1899        let dim = dim.to_index(self.shape(), "index-select")?;
1900        let indexes_len = match indexes.dims() {
1901            [l] => *l,
1902            _ => Err(Error::ShapeMismatchBinaryOp {
1903                lhs: self.shape().clone(),
1904                rhs: indexes.shape().clone(),
1905                op: "index-select",
1906            }
1907            .bt())?,
1908        };
1909        let storage = self.storage().index_select(
1910            &indexes.storage(),
1911            self.layout(),
1912            indexes.layout(),
1913            dim,
1914        )?;
1915        let mut dims = self.dims().to_vec();
1916        dims[dim] = indexes_len;
1917        let op = BackpropOp::new2(self, indexes, |t1, t2| Op::IndexSelect(t1, t2, dim));
1918        Ok(from_storage(storage, dims, op, false))
1919    }
1920
1921    /// Returns an iterator over position of the elements in the storage when ranging over the
1922    /// index tuples in lexicographic order.
1923    pub fn strided_index(&self) -> crate::StridedIndex<'_> {
1924        self.layout.strided_index()
1925    }
1926
1927    /// Similar to `strided_index` but returns the position of the start of each contiguous block
1928    /// as well as the length of the contiguous blocks. For a contiguous tensor, the index iterator
1929    /// will only return the start offset and the size would be the number of elements in the
1930    /// tensor.
1931    pub fn strided_blocks(&self) -> crate::StridedBlocks<'_> {
1932        self.layout.strided_blocks()
1933    }
1934
1935    /// Returns the data contained in a 1D tensor as a vector of scalar values.
1936    pub fn to_vec1<S: crate::WithDType>(&self) -> Result<Vec<S>> {
1937        if self.rank() != 1 {
1938            Err(Error::UnexpectedNumberOfDims {
1939                expected: 1,
1940                got: self.rank(),
1941                shape: self.shape().clone(),
1942            }
1943            .bt())?
1944        }
1945        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
1946            let data = S::cpu_storage_as_slice(cpu_storage)?;
1947            let data = match self.layout.contiguous_offsets() {
1948                Some((o1, o2)) => data[o1..o2].to_vec(),
1949                None => self.strided_index().map(|i| data[i]).collect(),
1950            };
1951            Ok::<Vec<_>, Error>(data)
1952        };
1953        match &*self.storage() {
1954            Storage::Cpu(storage) => from_cpu_storage(storage),
1955            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1956            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1957            #[cfg(feature = "rocm")]
1958            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1959            #[cfg(feature = "vulkan")]
1960            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1961            #[cfg(feature = "wgpu")]
1962            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1963        }
1964    }
1965
1966    /// Returns the data contained in a 2D tensor as a vector of vector of scalar values.
1967    pub fn to_vec2<S: crate::WithDType>(&self) -> Result<Vec<Vec<S>>> {
1968        let (dim1, dim2) = self.dims2()?;
1969        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
1970            let data = S::cpu_storage_as_slice(cpu_storage)?;
1971            let mut rows = vec![];
1972            match self.layout.contiguous_offsets() {
1973                Some((o1, o2)) => {
1974                    let data = &data[o1..o2];
1975                    for idx_row in 0..dim1 {
1976                        rows.push(data[idx_row * dim2..(idx_row + 1) * dim2].to_vec())
1977                    }
1978                }
1979                None => {
1980                    let mut src_index = self.strided_index();
1981                    for _idx_row in 0..dim1 {
1982                        let row = (0..dim2).map(|_| data[src_index.next().unwrap()]).collect();
1983                        rows.push(row)
1984                    }
1985                    assert!(src_index.next().is_none());
1986                }
1987            }
1988            Ok(rows)
1989        };
1990        match &*self.storage() {
1991            Storage::Cpu(storage) => from_cpu_storage(storage),
1992            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1993            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1994            #[cfg(feature = "rocm")]
1995            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1996            #[cfg(feature = "vulkan")]
1997            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1998            #[cfg(feature = "wgpu")]
1999            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2000        }
2001    }
2002
2003    /// Returns the data contained in a 3D tensor.
2004    pub fn to_vec3<S: crate::WithDType>(&self) -> Result<Vec<Vec<Vec<S>>>> {
2005        let (dim1, dim2, dim3) = self.dims3()?;
2006        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
2007            let data = S::cpu_storage_as_slice(cpu_storage)?;
2008            let mut top_rows = vec![];
2009            match self.layout.contiguous_offsets() {
2010                Some((o1, o2)) => {
2011                    let data = &data[o1..o2];
2012                    let dim23 = dim2 * dim3;
2013                    for idx1 in 0..dim1 {
2014                        let data = &data[idx1 * dim23..(idx1 + 1) * dim23];
2015                        let mut rows = vec![];
2016                        for idx2 in 0..dim2 {
2017                            rows.push(data[idx2 * dim3..(idx2 + 1) * dim3].to_vec())
2018                        }
2019                        top_rows.push(rows);
2020                    }
2021                }
2022                None => {
2023                    let mut src_index = self.strided_index();
2024                    for _idx in 0..dim1 {
2025                        let mut rows = vec![];
2026                        for _jdx in 0..dim2 {
2027                            let row = (0..dim3).map(|_| data[src_index.next().unwrap()]).collect();
2028                            rows.push(row)
2029                        }
2030                        top_rows.push(rows);
2031                    }
2032                    assert!(src_index.next().is_none());
2033                }
2034            }
2035            Ok(top_rows)
2036        };
2037        match &*self.storage() {
2038            Storage::Cpu(storage) => from_cpu_storage(storage),
2039            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2040            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2041            #[cfg(feature = "rocm")]
2042            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2043            #[cfg(feature = "vulkan")]
2044            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2045            #[cfg(feature = "wgpu")]
2046            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2047        }
2048    }
2049
2050    /// The dtype for the elements stored in the input tensor.
2051    pub fn dtype(&self) -> DType {
2052        self.dtype
2053    }
2054
2055    /// The device on which the input tensor is located.
2056    pub fn device(&self) -> &Device {
2057        &self.device
2058    }
2059
2060    /// The tensor shape, i.e. dimension sizes on each axis.
2061    pub fn shape(&self) -> &Shape {
2062        self.layout().shape()
2063    }
2064
2065    /// The dimension size for this tensor on each axis.
2066    pub fn dims(&self) -> &[usize] {
2067        self.shape().dims()
2068    }
2069
2070    /// The dimension size for a specified dimension index.
2071    pub fn dim<D: Dim>(&self, dim: D) -> Result<usize> {
2072        let dim = dim.to_index(self.shape(), "dim")?;
2073        Ok(self.dims()[dim])
2074    }
2075
2076    /// The layout of the input tensor, this stores both the shape of the tensor as well as the
2077    /// strides and the start offset to apply to the underlying storage.
2078    pub fn layout(&self) -> &Layout {
2079        &self.layout
2080    }
2081
2082    pub fn stride(&self) -> &[usize] {
2083        self.layout.stride()
2084    }
2085
2086    /// The number of dimensions for this tensor, 0 for a scalar tensor, 1 for a 1D tensor, etc.
2087    pub fn rank(&self) -> usize {
2088        self.shape().rank()
2089    }
2090
2091    /// The number of elements stored in this tensor.
2092    pub fn elem_count(&self) -> usize {
2093        self.shape().elem_count()
2094    }
2095
2096    /// The unique identifier for this tensor.
2097    pub fn id(&self) -> TensorId {
2098        self.id
2099    }
2100
2101    /// Whether this tensor is a variable or not. A variable is a tensor for which gradient is
2102    /// tracked and on which backpropagation can be performed.
2103    pub fn is_variable(&self) -> bool {
2104        self.is_variable
2105    }
2106
2107    pub(crate) fn op(&self) -> &Option<Op> {
2108        &self.op
2109    }
2110
2111    /// Computes the max of all the elements in this tensor and returns a tensor holding this
2112    /// scalar with zero dimensions.
2113    ///
2114    /// ```rust
2115    /// use hanzo_ml::{Tensor, Device};
2116    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2117    /// let tensor = tensor.max_all()?;
2118    /// assert_eq!(tensor.to_scalar::<f32>()?, 5.);
2119    /// # Ok::<(), hanzo_ml::Error>(())
2120    /// ```
2121    pub fn max_all(&self) -> Result<Tensor> {
2122        if self.rank() == 0 {
2123            Ok(self.clone())
2124        } else {
2125            self.flatten_all()?.max(0)
2126        }
2127    }
2128
2129    /// Computes the min of all the elements in this tensor and returns a tensor holding this
2130    /// scalar with zero dimensions.
2131    ///
2132    /// ```rust
2133    /// use hanzo_ml::{Tensor, Device};
2134    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2135    /// let tensor = tensor.min_all()?;
2136    /// assert_eq!(tensor.to_scalar::<f32>()?, 0.);
2137    /// # Ok::<(), hanzo_ml::Error>(())
2138    /// ```
2139    pub fn min_all(&self) -> Result<Tensor> {
2140        if self.rank() == 0 {
2141            Ok(self.clone())
2142        } else {
2143            self.flatten_all()?.min(0)
2144        }
2145    }
2146
2147    /// Computes the sum of all the elements in this tensor and returns a tensor holding this
2148    /// scalar with zero dimensions.
2149    ///
2150    /// ```rust
2151    /// use hanzo_ml::{Tensor, Device};
2152    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2153    /// let tensor = tensor.sum_all()?;
2154    /// assert_eq!(tensor.to_scalar::<f32>()?, 15.);
2155    /// # Ok::<(), hanzo_ml::Error>(())
2156    /// ```
2157    pub fn sum_all(&self) -> Result<Tensor> {
2158        let dims: Vec<_> = (0..self.rank()).collect();
2159        self.sum(dims)
2160    }
2161
2162    pub fn mean_all(&self) -> Result<Tensor> {
2163        self.sum_all()? / self.elem_count() as f64
2164    }
2165
2166    fn flatten_<D1: Dim, D2: Dim>(
2167        &self,
2168        start_dim: Option<D1>,
2169        end_dim: Option<D2>,
2170    ) -> Result<Tensor> {
2171        if self.rank() == 0 {
2172            self.reshape(1)
2173        } else {
2174            let start_dim = match start_dim {
2175                None => 0,
2176                Some(dim) => dim.to_index(self.shape(), "flatten")?,
2177            };
2178            let end_dim = match end_dim {
2179                None => self.rank() - 1,
2180                Some(dim) => dim.to_index(self.shape(), "flatten")?,
2181            };
2182            if start_dim < end_dim {
2183                let dims = self.dims();
2184                let mut dst_dims = dims[..start_dim].to_vec();
2185                dst_dims.push(dims[start_dim..end_dim + 1].iter().product::<usize>());
2186                if end_dim + 1 < dims.len() {
2187                    dst_dims.extend(&dims[end_dim + 1..]);
2188                }
2189                self.reshape(dst_dims)
2190            } else {
2191                Ok(self.clone())
2192            }
2193        }
2194    }
2195
2196    /// Flattens the input tensor on the dimension indexes from `start_dim` to `end_dim` (both
2197    /// inclusive).
2198    pub fn flatten<D1: Dim, D2: Dim>(&self, start_dim: D1, end_dim: D2) -> Result<Tensor> {
2199        self.flatten_(Some(start_dim), Some(end_dim))
2200    }
2201
2202    /// Flattens the input tensor on the dimension indexes from `0` to `end_dim` (inclusive).
2203    pub fn flatten_to<D: Dim>(&self, end_dim: D) -> Result<Tensor> {
2204        self.flatten_(None::<usize>, Some(end_dim))
2205    }
2206
2207    /// Flattens the input tensor on the dimension indexes from `start_dim` (inclusive) to the last
2208    /// dimension.
2209    pub fn flatten_from<D: Dim>(&self, start_dim: D) -> Result<Tensor> {
2210        self.flatten_(Some(start_dim), None::<usize>)
2211    }
2212
2213    /// Flattens the input tensor by reshaping it into a one dimension tensor.
2214    ///
2215    /// ```rust
2216    /// use hanzo_ml::{Tensor, Device};
2217    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2218    /// let tensor = tensor.flatten_all()?;
2219    /// assert_eq!(tensor.to_vec1::<f32>()?, &[0., 1., 2., 3., 4., 5.]);
2220    /// # Ok::<(), hanzo_ml::Error>(())
2221    /// ```
2222    pub fn flatten_all(&self) -> Result<Tensor> {
2223        self.flatten_(None::<usize>, None::<usize>)
2224    }
2225
2226    /// Returns the sub-tensor fixing the index at `i` on the first dimension.
2227    ///
2228    /// ```rust
2229    /// use hanzo_ml::{Tensor, Device};
2230    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2231    /// let t = tensor.get(0)?;
2232    /// assert_eq!(t.to_vec1::<f32>()?, &[0., 1.]);
2233    /// let t = tensor.get(1)?;
2234    /// assert_eq!(t.to_vec1::<f32>()?, &[2., 3.]);
2235    /// # Ok::<(), hanzo_ml::Error>(())
2236    /// ```
2237    pub fn get(&self, i: usize) -> Result<Tensor> {
2238        let dims = self.dims();
2239        if dims.is_empty() {
2240            Ok(self.clone())
2241        } else {
2242            self.narrow(0, i, 1)?.reshape(&dims[1..])
2243        }
2244    }
2245
2246    /// Returns the sub-tensor fixing the index at `index` on the dimension `dim`.
2247    ///
2248    /// ```rust
2249    /// use hanzo_ml::{Tensor, Device};
2250    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2251    /// let t = tensor.get_on_dim(1, 0)?;
2252    /// assert_eq!(t.to_vec1::<f32>()?, &[0., 2., 4.]);
2253    /// let t = tensor.get_on_dim(1, 1)?;
2254    /// assert_eq!(t.to_vec1::<f32>()?, &[1., 3., 5.]);
2255    /// let t = tensor.get_on_dim(0, 1)?;
2256    /// assert_eq!(t.to_vec1::<f32>()?, &[2., 3.]);
2257    /// # Ok::<(), hanzo_ml::Error>(())
2258    /// ```
2259    pub fn get_on_dim<D: Dim>(&self, dim: D, index: usize) -> Result<Tensor> {
2260        let dim = dim.to_index(self.shape(), "get_on_dim")?;
2261        self.narrow(dim, index, 1)?.squeeze(dim)
2262    }
2263
2264    /// Returns a tensor that is a transposed version of the input, the two last dimensions of the
2265    /// input are swapped.
2266    ///
2267    /// ```rust
2268    /// use hanzo_ml::{Tensor, Device};
2269    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2270    /// let tensor = tensor.t()?;
2271    /// assert_eq!(tensor.to_vec2::<f32>()?, &[[0.0, 2.0, 4.0], [1.0, 3.0, 5.0]]);
2272    /// # Ok::<(), hanzo_ml::Error>(())
2273    /// ```
2274    pub fn t(&self) -> Result<Tensor> {
2275        let rank = self.rank();
2276        if rank < 2 {
2277            Err(Error::UnexpectedNumberOfDims {
2278                expected: 2,
2279                got: rank,
2280                shape: self.shape().clone(),
2281            }
2282            .bt())?
2283        }
2284        self.transpose(rank - 2, rank - 1)
2285    }
2286
2287    /// Returns a tensor that is a transposed version of the input, the given dimensions are
2288    /// swapped.
2289    pub fn transpose<D1: Dim, D2: Dim>(&self, dim1: D1, dim2: D2) -> Result<Tensor> {
2290        let dim1 = dim1.to_index(self.shape(), "transpose")?;
2291        let dim2 = dim2.to_index(self.shape(), "transpose")?;
2292        if dim1 == dim2 {
2293            return Ok(self.clone());
2294        }
2295        let op = BackpropOp::new1(self, |t| Op::Transpose(t, dim1, dim2));
2296        let tensor_ = Tensor_ {
2297            id: TensorId::new(),
2298            storage: self.storage.clone(),
2299            layout: self.layout.transpose(dim1, dim2)?,
2300            op,
2301            is_variable: false,
2302            dtype: self.dtype,
2303            device: self.device.clone(),
2304        };
2305        Ok(Tensor(Arc::new(tensor_)))
2306    }
2307
2308    /// Returns a tensor with the same data as the input where the dimensions have been permuted.
2309    /// dims must be a permutation, i.e. include each dimension index exactly once.
2310    ///
2311    /// ```rust
2312    /// use hanzo_ml::{Tensor, Device};
2313    /// let tensor = Tensor::arange(0u32, 120u32, &Device::Cpu)?.reshape((2, 3, 4, 5))?;
2314    /// assert_eq!(tensor.dims(), &[2, 3, 4, 5]);
2315    /// let tensor = tensor.permute((2, 3, 1, 0))?;
2316    /// assert_eq!(tensor.dims(), &[4, 5, 3, 2]);
2317    /// # Ok::<(), hanzo_ml::Error>(())
2318    /// ```
2319    pub fn permute<D: Dims>(&self, dims: D) -> Result<Tensor> {
2320        let dims = dims.to_indexes(self.shape(), "permute")?;
2321        // O(n^2) permutation check but these arrays are small.
2322        let is_permutation =
2323            dims.len() == self.rank() && (0..dims.len()).all(|i| dims.contains(&i));
2324        if !is_permutation {
2325            bail!(
2326                "dimension mismatch in permute, tensor {:?}, dims: {:?}",
2327                self.dims(),
2328                dims
2329            )
2330        }
2331        let op = BackpropOp::new1(self, |t| Op::Permute(t, dims.clone()));
2332        let tensor_ = Tensor_ {
2333            id: TensorId::new(),
2334            storage: self.storage.clone(),
2335            layout: self.layout.permute(&dims)?,
2336            op,
2337            is_variable: false,
2338            dtype: self.dtype,
2339            device: self.device.clone(),
2340        };
2341        Ok(Tensor(Arc::new(tensor_)))
2342    }
2343
2344    /// Returns true if the data is stored in a C contiguous (aka row major) way.
2345    pub fn is_contiguous(&self) -> bool {
2346        self.layout.is_contiguous()
2347    }
2348
2349    /// Returns true if the data is stored in a Fortran contiguous (aka column major) way.
2350    pub fn is_fortran_contiguous(&self) -> bool {
2351        self.layout.is_fortran_contiguous()
2352    }
2353
2354    /// Compared to clone, this copies the actual storage but may fail because of running out of
2355    /// memory.
2356    pub fn copy(&self) -> Result<Tensor> {
2357        let op = BackpropOp::new1(self, Op::Copy);
2358        let tensor_ = Tensor_ {
2359            id: TensorId::new(),
2360            storage: Arc::new(RwLock::new(self.storage().try_clone(self.layout())?)),
2361            layout: self.layout.clone(),
2362            op,
2363            is_variable: false,
2364            dtype: self.dtype,
2365            device: self.device.clone(),
2366        };
2367        Ok(Tensor(Arc::new(tensor_)))
2368    }
2369
2370    /// Returns a new tensor detached from the current graph, gradient are not propagated through
2371    /// this new node. The storage of this tensor is shared with the initial tensor.
2372    ///
2373    /// If the tensor is already detached from the computation graph, the same tensor is returned.
2374    pub fn detach(&self) -> Tensor {
2375        if self.op.is_none() && !self.is_variable {
2376            self.clone()
2377        } else {
2378            let tensor_ = Tensor_ {
2379                id: TensorId::new(),
2380                storage: self.storage.clone(),
2381                layout: self.layout.clone(),
2382                op: BackpropOp::none(),
2383                is_variable: false,
2384                dtype: self.dtype,
2385                device: self.device.clone(),
2386            };
2387            Tensor(Arc::new(tensor_))
2388        }
2389    }
2390
2391    /// If the target device is the same as the tensor device, only a shallow copy is performed.
2392    pub fn to_device(&self, device: &Device) -> Result<Tensor> {
2393        if self.device().same_device(device) {
2394            Ok(self.clone())
2395        } else {
2396            let storage = match (&*self.storage(), device) {
2397                (Storage::Cpu(storage), Device::Cuda(cuda)) => {
2398                    Storage::Cuda(cuda.storage_from_cpu_storage(storage)?)
2399                }
2400                (Storage::Cpu(storage), Device::Metal(metal)) => {
2401                    Storage::Metal(metal.storage_from_cpu_storage(storage)?)
2402                }
2403                (Storage::Cuda(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2404                (Storage::Metal(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2405                #[cfg(feature = "rocm")]
2406                (Storage::Rocm(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2407                #[cfg(feature = "vulkan")]
2408                (Storage::Vulkan(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2409                #[cfg(feature = "wgpu")]
2410                (Storage::Wgpu(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2411                #[cfg(feature = "rocm")]
2412                (Storage::Cpu(storage), Device::Rocm(rocm)) => {
2413                    Storage::Rocm(rocm.storage_from_cpu_storage(storage)?)
2414                }
2415                #[cfg(feature = "vulkan")]
2416                (Storage::Cpu(storage), Device::Vulkan(vulkan)) => {
2417                    Storage::Vulkan(vulkan.storage_from_cpu_storage(storage)?)
2418                }
2419                #[cfg(feature = "wgpu")]
2420                (Storage::Cpu(storage), Device::Wgpu(wgpu)) => {
2421                    Storage::Wgpu(wgpu.storage_from_cpu_storage(storage)?)
2422                }
2423                #[cfg(feature = "rocm")]
2424                (Storage::Rocm(storage), Device::Rocm(rocm)) => {
2425                    let cpu_storage = storage.to_cpu_storage()?;
2426                    Storage::Rocm(rocm.storage_from_cpu_storage(&cpu_storage)?)
2427                }
2428                #[cfg(feature = "vulkan")]
2429                (Storage::Vulkan(storage), Device::Vulkan(vulkan)) => {
2430                    let cpu_storage = storage.to_cpu_storage()?;
2431                    Storage::Vulkan(vulkan.storage_from_cpu_storage(&cpu_storage)?)
2432                }
2433                #[cfg(feature = "wgpu")]
2434                (Storage::Wgpu(storage), Device::Wgpu(wgpu)) => {
2435                    let cpu_storage = storage.to_cpu_storage()?;
2436                    Storage::Wgpu(wgpu.storage_from_cpu_storage(&cpu_storage)?)
2437                }
2438                (Storage::Cuda(storage), Device::Cuda(cuda)) => {
2439                    // TODO: Avoid passing through the cpu storage here, especially if the gpu ids
2440                    // are the same.
2441                    let cpu_storage = storage.to_cpu_storage()?;
2442                    Storage::Cuda(cuda.storage_from_cpu_storage(&cpu_storage)?)
2443                }
2444                (Storage::Cpu(storage), Device::Cpu) => Storage::Cpu(storage.clone()),
2445                _ => {
2446                    bail!(
2447                        "not implemented yet, self.device: {:?}, device: {:?}",
2448                        self.device(),
2449                        device
2450                    )
2451                }
2452            };
2453            let op = BackpropOp::new1(self, Op::ToDevice);
2454            let tensor_ = Tensor_ {
2455                id: TensorId::new(),
2456                storage: Arc::new(RwLock::new(storage)),
2457                layout: self.layout.clone(),
2458                op,
2459                is_variable: false,
2460                dtype: self.dtype,
2461                device: device.clone(),
2462            };
2463            Ok(Tensor(Arc::new(tensor_)))
2464        }
2465    }
2466
2467    /// Returns a new tensor duplicating data from the original tensor. New dimensions are inserted
2468    /// on the left.
2469    pub fn broadcast_left<S: Into<Shape>>(&self, left_shape: S) -> Result<Self> {
2470        let left_shape = left_shape.into();
2471        let mut dims = left_shape.into_dims();
2472        dims.extend(self.dims());
2473        self.broadcast_as(dims)
2474    }
2475
2476    /// Broadcast the input tensor to the target shape. This returns an error if the input shape is
2477    /// not compatible with the target shape.
2478    ///
2479    /// If the input shape is `i_1, i_2, ... i_k`, the target shape has to have `k` dimensions or
2480    /// more and shape `j_1, ..., j_l, t_1, t_2, ..., t_k`. The dimensions `j_1` to `j_l` can have
2481    /// any value, the dimension `t_a` must be equal to `i_a` if `i_a` is different from 1. If
2482    /// `i_a` is equal to 1, any value can be used.
2483    pub fn broadcast_as<S: Into<Shape>>(&self, shape: S) -> Result<Self> {
2484        let tensor_ = Tensor_ {
2485            id: TensorId::new(),
2486            storage: self.storage.clone(),
2487            layout: self.layout.broadcast_as(shape)?,
2488            op: BackpropOp::new1(self, Op::Broadcast),
2489            is_variable: false,
2490            dtype: self.dtype,
2491            device: self.device.clone(),
2492        };
2493        Ok(Tensor(Arc::new(tensor_)))
2494    }
2495
2496    /// An alias for broadcast_as.
2497    pub fn expand<S: Into<Shape>>(&self, shape: S) -> Result<Self> {
2498        self.broadcast_as(shape)
2499    }
2500
2501    /// Casts the input tensor to the target `dtype`.
2502    ///
2503    /// ```rust
2504    /// use hanzo_ml::{Tensor, Device};
2505    /// let tensor = Tensor::new(3.14159265358979f64, &Device::Cpu)?;
2506    /// assert_eq!(tensor.to_scalar::<f64>()?, 3.14159265358979);
2507    /// let tensor = tensor.to_dtype(hanzo_ml::DType::F32)?;
2508    /// assert_eq!(tensor.to_scalar::<f32>()?, 3.1415927);
2509    /// # Ok::<(), hanzo_ml::Error>(())
2510    /// ```
2511    pub fn to_dtype(&self, dtype: DType) -> Result<Self> {
2512        if self.dtype() == dtype {
2513            Ok(self.clone())
2514        } else {
2515            let shape = self.shape();
2516            let storage = self.storage().to_dtype(self.layout(), dtype)?;
2517            let op = BackpropOp::new1(self, Op::ToDType);
2518            Ok(from_storage(storage, shape.clone(), op, false))
2519        }
2520    }
2521
2522    /// Returns a tensor that is in row major order. This is the same as the original tensor if it
2523    /// was already contiguous, otherwise a copy is triggered.
2524    pub fn contiguous(&self) -> Result<Tensor> {
2525        if self.is_contiguous() {
2526            Ok(self.clone())
2527        } else {
2528            let shape = self.shape();
2529            let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
2530            self.storage()
2531                .copy_strided_src(&mut storage, 0, self.layout())?;
2532            let op = BackpropOp::new1(self, Op::Copy);
2533            Ok(from_storage(storage, shape.clone(), op, false))
2534        }
2535    }
2536
2537    /// Returns a tensor that is in row major order. This always makes a copy.
2538    pub fn force_contiguous(&self) -> Result<Tensor> {
2539        let shape = self.shape();
2540        let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
2541        self.storage()
2542            .copy_strided_src(&mut storage, 0, self.layout())?;
2543        let op = BackpropOp::new1(self, Op::Copy);
2544        Ok(from_storage(storage, shape.clone(), op, false))
2545    }
2546
2547    /// Create a variable based on the values currently stored in a tensor. The storage is always
2548    /// copied.
2549    pub(crate) fn make_var(&self) -> Result<Tensor> {
2550        let shape = self.shape().clone();
2551        let mut storage = unsafe { self.device().alloc_uninit(&shape, self.dtype())? };
2552        self.storage()
2553            .copy_strided_src(&mut storage, 0, self.layout())?;
2554        Ok(from_storage(storage, shape, BackpropOp::none(), true))
2555    }
2556
2557    /// Reshape returns a tensor with the target shape provided that the number of elements of the
2558    /// original tensor is the same.
2559    /// If the input tensor is contiguous, this is a view on the original data. Otherwise this uses
2560    /// a new storage and copies the data over, the returned tensor is always contiguous.
2561    ///
2562    /// The shape can be specified using a tuple of `usize` and at most one `()` in which case
2563    /// the behavior is the same as when using `-1` in PyTorch: this dimension size is adjusted so
2564    /// as to match the number of elements in the tensor.
2565    ///
2566    /// ```rust
2567    /// # use hanzo_ml::{Tensor, DType, Device, D};
2568    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2569    ///
2570    /// let c = a.reshape((1, 6))?;
2571    /// assert_eq!(c.shape().dims(), &[1, 6]);
2572    ///
2573    /// let c = a.reshape((3, 2))?;
2574    /// assert_eq!(c.shape().dims(), &[3, 2]);
2575    ///
2576    /// let c = a.reshape((2, (), 1))?;
2577    /// assert_eq!(c.shape().dims(), &[2, 3, 1]);
2578    ///
2579    /// # Ok::<(), hanzo_ml::Error>(())
2580    /// ```
2581    pub fn reshape<S: ShapeWithOneHole>(&self, s: S) -> Result<Tensor> {
2582        let shape = s.into_shape(self.elem_count())?;
2583        if shape.elem_count() != self.elem_count() {
2584            return Err(Error::ShapeMismatchBinaryOp {
2585                lhs: self.shape().clone(),
2586                rhs: shape,
2587                op: "reshape",
2588            }
2589            .bt());
2590        }
2591        let op = BackpropOp::new1(self, Op::Reshape);
2592        if self.is_contiguous() {
2593            let tensor_ = Tensor_ {
2594                id: TensorId::new(),
2595                storage: self.storage.clone(),
2596                layout: Layout::contiguous_with_offset(shape, self.layout.start_offset()),
2597                op,
2598                is_variable: false,
2599                dtype: self.dtype,
2600                device: self.device.clone(),
2601            };
2602            Ok(Tensor(Arc::new(tensor_)))
2603        } else {
2604            let mut storage = unsafe { self.device().alloc_uninit(&shape, self.dtype())? };
2605            self.storage()
2606                .copy_strided_src(&mut storage, 0, self.layout())?;
2607            Ok(from_storage(storage, shape, op, false))
2608        }
2609    }
2610
2611    /// Creates a new tensor with the specified dimension removed if its size was one.
2612    ///
2613    /// ```rust
2614    /// # use hanzo_ml::{Tensor, DType, Device, D};
2615    /// let a = Tensor::zeros((2, 3, 1), DType::F32, &Device::Cpu)?;
2616    ///
2617    /// let c = a.squeeze(2)?;
2618    /// assert_eq!(c.shape().dims(), &[2, 3]);
2619    ///
2620    /// let c = a.squeeze(D::Minus1)?;
2621    /// assert_eq!(c.shape().dims(), &[2, 3]);
2622    /// # Ok::<(), hanzo_ml::Error>(())
2623    /// ```
2624    pub fn squeeze<D: Dim>(&self, dim: D) -> Result<Self> {
2625        // The PyTorch semantics are to return the same tensor if the target dimension
2626        // does not have a size of 1.
2627        let dims = self.dims();
2628        let dim = dim.to_index(self.shape(), "squeeze")?;
2629        if dims[dim] == 1 {
2630            let mut dims = dims.to_vec();
2631            let mut strides = self.stride().to_vec();
2632            dims.remove(dim);
2633            strides.remove(dim);
2634            let tensor_ = Tensor_ {
2635                id: TensorId::new(),
2636                storage: self.storage.clone(),
2637                layout: Layout::new(dims.into(), strides, self.layout.start_offset()),
2638                op: BackpropOp::new1(self, Op::Reshape),
2639                is_variable: false,
2640                dtype: self.dtype,
2641                device: self.device.clone(),
2642            };
2643            Ok(Tensor(Arc::new(tensor_)))
2644        } else {
2645            Ok(self.clone())
2646        }
2647    }
2648
2649    /// Creates a new tensor with a dimension of size one inserted at the specified position.
2650    ///
2651    /// ```rust
2652    /// # use hanzo_ml::{Tensor, DType, Device, D};
2653    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2654    ///
2655    /// let c = a.unsqueeze(0)?;
2656    /// assert_eq!(c.shape().dims(), &[1, 2, 3]);
2657    ///
2658    /// let c = a.unsqueeze(D::Minus1)?;
2659    /// assert_eq!(c.shape().dims(), &[2, 3, 1]);
2660    /// # Ok::<(), hanzo_ml::Error>(())
2661    /// ```
2662    pub fn unsqueeze<D: Dim>(&self, dim: D) -> Result<Self> {
2663        let mut dims = self.dims().to_vec();
2664        let mut strides = self.stride().to_vec();
2665        let dim = dim.to_index_plus_one(self.shape(), "unsqueeze")?;
2666        // Cannot panic because to_index_plus_one already checks dimensions
2667        dims.insert(dim, 1);
2668        // Any stride would work here, but we pick one so as to maximize the probability to remain
2669        // C contiguous.
2670        let stride = if dim < strides.len() { strides[dim] } else { 1 };
2671        strides.insert(dim, stride);
2672        let tensor_ = Tensor_ {
2673            id: TensorId::new(),
2674            storage: self.storage.clone(),
2675            layout: Layout::new(dims.into(), strides, self.layout.start_offset()),
2676            op: BackpropOp::new1(self, Op::Reshape),
2677            is_variable: false,
2678            dtype: self.dtype,
2679            device: self.device.clone(),
2680        };
2681        Ok(Tensor(Arc::new(tensor_)))
2682    }
2683
2684    /// Stacks two or more tensors along a particular dimension.
2685    ///
2686    /// All tensors must have the same rank, and the output has one additional rank
2687    ///
2688    /// ```rust
2689    /// # use hanzo_ml::{Tensor, DType, Device};
2690    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2691    /// let b = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2692    ///
2693    /// let c = Tensor::stack(&[&a, &b], 0)?;
2694    /// assert_eq!(c.shape().dims(), &[2, 2, 3]);
2695    ///
2696    /// let c = Tensor::stack(&[&a, &b], 2)?;
2697    /// assert_eq!(c.shape().dims(), &[2, 3, 2]);
2698    /// # Ok::<(), hanzo_ml::Error>(())
2699    /// ```
2700    pub fn stack<A: AsRef<Tensor>, D: Dim>(args: &[A], dim: D) -> Result<Self> {
2701        if args.is_empty() {
2702            Err(Error::OpRequiresAtLeastOneTensor { op: "stack" }.bt())?
2703        }
2704        let dim = dim.to_index_plus_one(args[0].as_ref().shape(), "stack")?;
2705        let args = args
2706            .iter()
2707            .map(|t| t.as_ref().unsqueeze(dim))
2708            .collect::<Result<Vec<_>>>()?;
2709        Self::cat(&args, dim)
2710    }
2711
2712    /// Pad the input tensor using 0s along dimension `dim`. This adds `left` elements before the
2713    /// input tensor values and `right` elements after.
2714    pub fn pad_with_zeros<D: Dim>(&self, dim: D, left: usize, right: usize) -> Result<Self> {
2715        if left == 0 && right == 0 {
2716            Ok(self.clone())
2717        } else if left == 0 {
2718            let dim = dim.to_index(self.shape(), "pad_with_zeros")?;
2719            let mut dims = self.dims().to_vec();
2720            dims[dim] = right;
2721            let right = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2722            Tensor::cat(&[self, &right], dim)
2723        } else if right == 0 {
2724            let dim = dim.to_index(self.shape(), "pad_with_zeros")?;
2725            let mut dims = self.dims().to_vec();
2726            dims[dim] = left;
2727            let left = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2728            Tensor::cat(&[&left, self], dim)
2729        } else {
2730            let dim = dim.to_index(self.shape(), "pad_with_zeros")?;
2731            let mut dims = self.dims().to_vec();
2732            dims[dim] = left;
2733            let left = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2734            dims[dim] = right;
2735            let right = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2736            Tensor::cat(&[&left, self, &right], dim)
2737        }
2738    }
2739
2740    /// Pad the input tensor using same values along dimension `dim`. This adds `left` elements before the
2741    /// input tensor values and `right` elements after.
2742    pub fn pad_with_same<D: Dim>(&self, dim: D, left: usize, right: usize) -> Result<Self> {
2743        if left == 0 && right == 0 {
2744            Ok(self.clone())
2745        } else if self.elem_count() == 0 {
2746            bail!("cannot use pad_with_same on an empty tensor")
2747        } else if left == 0 {
2748            let dim = dim.to_index(self.shape(), "pad_with_same")?;
2749            let r = self.narrow(dim, self.dim(dim)? - 1, 1)?;
2750            let mut v = vec![self];
2751            for _ in 0..right {
2752                v.push(&r)
2753            }
2754            Tensor::cat(&v, dim)
2755        } else if right == 0 {
2756            let dim = dim.to_index(self.shape(), "pad_with_same")?;
2757            let l = self.narrow(dim, 0, 1)?;
2758            let mut v = vec![];
2759            for _ in 0..left {
2760                v.push(&l)
2761            }
2762            v.push(self);
2763            Tensor::cat(&v, dim)
2764        } else {
2765            let dim = dim.to_index(self.shape(), "pad_with_same")?;
2766            let l = self.narrow(dim, 0, 1)?;
2767            let r = self.narrow(dim, self.dim(dim)? - 1, 1)?;
2768            let mut v = vec![];
2769            for _ in 0..left {
2770                v.push(&l)
2771            }
2772            v.push(self);
2773            for _ in 0..right {
2774                v.push(&r)
2775            }
2776            Tensor::cat(&v, dim)
2777        }
2778    }
2779
2780    /// Run the `forward` method of `m` on `self`.
2781    pub fn apply<M: crate::Module>(&self, m: &M) -> Result<Self> {
2782        m.forward(self)
2783    }
2784
2785    /// Run the `forward` method of `m` on `self`.
2786    pub fn apply_t<M: crate::ModuleT>(&self, m: &M, train: bool) -> Result<Self> {
2787        m.forward_t(self, train)
2788    }
2789
2790    pub(crate) fn storage(&self) -> std::sync::RwLockReadGuard<'_, Storage> {
2791        self.storage.read().unwrap()
2792    }
2793
2794    pub(crate) fn storage_mut(&self) -> std::sync::RwLockWriteGuard<'_, Storage> {
2795        self.storage.write().unwrap()
2796    }
2797
2798    // If we extend the visibility of this function to be usable outside of this crate, we should
2799    // make it unsafe.
2800    pub(crate) fn storage_mut_and_layout(
2801        &self,
2802    ) -> (std::sync::RwLockWriteGuard<'_, Storage>, &Layout) {
2803        let storage = self.storage.write().unwrap();
2804        (storage, &self.layout)
2805    }
2806
2807    /// The storage used by this tensor, together with the layout to use to access it safely.
2808    pub fn storage_and_layout(&self) -> (std::sync::RwLockReadGuard<'_, Storage>, &Layout) {
2809        let storage = self.storage.read().unwrap();
2810        (storage, &self.layout)
2811    }
2812
2813    pub(crate) fn same_storage(&self, rhs: &Self) -> bool {
2814        let lhs: &RwLock<Storage> = self.storage.as_ref();
2815        let rhs: &RwLock<Storage> = rhs.storage.as_ref();
2816        std::ptr::eq(lhs, rhs)
2817    }
2818
2819    /// Normalize a 'relative' axis value: positive values are kept, negative
2820    /// values means counting the dimensions from the back.
2821    pub fn normalize_axis(&self, axis: i64) -> Result<usize> {
2822        let rank = self.rank() as i64;
2823        if rank <= axis {
2824            bail!("axis {axis} is too large, tensor rank {rank}")
2825        } else if 0 <= axis {
2826            Ok(axis as usize)
2827        } else {
2828            let naxis = rank + axis;
2829            if naxis < 0 {
2830                bail!("axis {axis} is too small, tensor rank {rank}")
2831            }
2832            Ok(naxis as usize)
2833        }
2834    }
2835
2836    /// Returns a lower triangular matrix of ones of size n by n.
2837    pub fn tril2(n: usize, dtype: DType, device: &Device) -> Result<Self> {
2838        let t = Tensor::arange(0u32, n as u32, device)?;
2839        let t1 = t.reshape((1, n))?.broadcast_as((n, n))?;
2840        let t2 = t.reshape((n, 1))?.broadcast_as((n, n))?;
2841        t1.le(&t2)?.to_dtype(dtype)
2842    }
2843
2844    /// Returns an upper triangular matrix of ones of size n by n.
2845    pub fn triu2(n: usize, dtype: DType, device: &Device) -> Result<Self> {
2846        let t = Tensor::arange(0u32, n as u32, device)?;
2847        let t1 = t.reshape((1, n))?.broadcast_as((n, n))?;
2848        let t2 = t.reshape((n, 1))?.broadcast_as((n, n))?;
2849        t1.ge(&t2)?.to_dtype(dtype)
2850    }
2851
2852    /// Returns a matrix with a diagonal of ones of size n by n.
2853    pub fn eye(n: usize, dtype: DType, device: &Device) -> Result<Self> {
2854        let t = Tensor::arange(0u32, n as u32, device)?;
2855        let t1 = t.reshape((1, n))?.broadcast_as((n, n))?;
2856        let t2 = t.reshape((n, 1))?.broadcast_as((n, n))?;
2857        t1.eq(&t2)?.to_dtype(dtype)
2858    }
2859
2860    /// Returns the cumulative sum of elements of the input tensor summed over the specified
2861    /// dimension.
2862    ///
2863    /// This operation is most efficient when dim is the last dimension of the tensor.
2864    pub fn cumsum<D: Dim>(&self, dim: D) -> Result<Self> {
2865        let dim = dim.to_index(self.shape(), "cumsum")?;
2866        let rank = self.rank();
2867        if rank == 0 {
2868            return Ok(self.clone());
2869        }
2870        let n_axis = self.dim(dim)?;
2871        let triu = Tensor::triu2(n_axis, self.dtype(), self.device())?;
2872        if rank == 1 {
2873            self.unsqueeze(0)?.matmul(&triu)?.squeeze(0)
2874        } else {
2875            let last = rank - 1;
2876            let t = self.transpose(dim, last)?;
2877            let t = t.broadcast_matmul(&triu)?;
2878            t.transpose(dim, last)
2879        }
2880    }
2881
2882    /// Returns a copy of `self` where the values within `ranges` have been replaced with the
2883    /// content of `src`.
2884    pub fn slice_assign<D: std::ops::RangeBounds<usize>>(
2885        &self,
2886        ranges: &[D],
2887        src: &Tensor,
2888    ) -> Result<Self> {
2889        let src_dims = src.dims();
2890        let self_dims = self.dims();
2891        if self_dims.len() != src_dims.len() {
2892            bail!(
2893                "slice-assign requires input with the same rank {} <> {}",
2894                self_dims.len(),
2895                src_dims.len()
2896            )
2897        }
2898        if self_dims.len() != ranges.len() {
2899            bail!(
2900                "slice-assign requires input with the same rank as there are ranges {} <> {}",
2901                self_dims.len(),
2902                ranges.len()
2903            )
2904        }
2905        let mut src = src.clone();
2906        let mut mask = Self::ones(src.shape(), DType::U8, src.device())?;
2907        for (i, range) in ranges.iter().enumerate() {
2908            let start_included = match range.start_bound() {
2909                std::ops::Bound::Unbounded => 0,
2910                std::ops::Bound::Included(v) => *v,
2911                std::ops::Bound::Excluded(v) => *v + 1,
2912            };
2913            let end_excluded = match range.end_bound() {
2914                std::ops::Bound::Unbounded => self_dims[i],
2915                std::ops::Bound::Included(v) => *v + 1,
2916                std::ops::Bound::Excluded(v) => *v,
2917            };
2918            if end_excluded <= start_included {
2919                bail!("slice-assign: empty range for dim {i}, {start_included} {end_excluded}")
2920            }
2921            if self_dims[i] < end_excluded {
2922                bail!(
2923                    "slice-assign: upper bound is out of range for dim {i}, {end_excluded} {}",
2924                    self_dims[i]
2925                )
2926            }
2927            if end_excluded - start_included != src_dims[i] {
2928                bail!(
2929                    "slice-assign: the range for dim {i} ({start_included}..{end_excluded}) does not match the size of src {}", src_dims[i]
2930                )
2931            }
2932            src = src.pad_with_zeros(i, start_included, self_dims[i] - end_excluded)?;
2933            mask = mask.pad_with_zeros(i, start_included, self_dims[i] - end_excluded)?
2934        }
2935        mask.where_cond(/* on_true= */ &src, /* on_false= */ self)
2936    }
2937
2938    /// Returns log(sum(exp(tensor), dim)).
2939    pub fn log_sum_exp<D: Dims>(&self, sum_dims: D) -> Result<Self> {
2940        let sum_dims = sum_dims.to_indexes(self.shape(), "log-sum-exp")?;
2941        if sum_dims.is_empty() {
2942            return Ok(self.clone());
2943        }
2944        let max = sum_dims[1..]
2945            .iter()
2946            .try_fold(self.max_keepdim(sum_dims[0])?, |max, &dim| {
2947                max.max_keepdim(dim)
2948            })?;
2949        let exp = self.broadcast_sub(&max)?.exp()?;
2950        let sum = exp.sum(sum_dims.clone())?;
2951
2952        sum.log()? + max.squeeze_dims(&sum_dims)
2953    }
2954
2955    /// Pointwise pow operation.
2956    pub fn pow(&self, rhs: &Tensor) -> Result<Self> {
2957        rhs.mul(&self.log()?)?.exp()
2958    }
2959
2960    /// Broadcasting version of `pow`.
2961    pub fn broadcast_pow(&self, rhs: &Tensor) -> Result<Self> {
2962        rhs.broadcast_mul(&self.log()?)?.exp()
2963    }
2964
2965    /// Returns a new tensor with the order of elements reversed along the specified dimensions.
2966    /// This function makes a copy of the tensor’s data.
2967    ///
2968    /// ```rust
2969    /// # use hanzo_ml::{Tensor, Device};
2970    /// let t = Tensor::arange(0., 6., &Device::Cpu)?.reshape((2, 3))?;
2971    /// assert_eq!(t.to_vec2::<f64>()?, &[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]]);
2972    /// let t_flipped = t.flip(&[0])?;
2973    /// assert_eq!(t_flipped.to_vec2::<f64>()?, &[[3.0, 4.0, 5.0], [0.0, 1.0, 2.0]]);
2974    /// # Ok::<(), hanzo_ml::Error>(())
2975    /// ```
2976    pub fn flip(&self, dims: &[usize]) -> Result<Tensor> {
2977        let mut result = self.clone();
2978        for &dim in dims.iter() {
2979            let size = result.dim(dim)?;
2980            let indices: Vec<i64> = (0..size).rev().map(|x| x as i64).collect();
2981            let indices_tensor = Tensor::from_vec(indices, (size,), result.device())?;
2982            result = result.index_select(&indices_tensor, dim)?;
2983        }
2984        Ok(result)
2985    }
2986
2987    /// Returns a view of which contains all slices of size `size` from self tensor in the dimension
2988    /// `dim` and stepped by `step`.
2989    pub fn unfold<D: Dim>(&self, dim: D, size: usize, step: usize) -> Result<Self> {
2990        // https://github.com/pytorch/pytorch/blob/75b0720a97ac5d82e8a7a1a6ae7c5f7a87d7183d/aten/src/ATen/native/TensorShape.cpp#L3785-L3804
2991        let mut sizes = self.dims().to_vec();
2992        let mut strides = self.stride().to_vec();
2993
2994        let dim = dim.to_index(self.shape(), "unfold")?;
2995
2996        let max_len = if self.dims().is_empty() {
2997            1
2998        } else {
2999            sizes[dim]
3000        };
3001        if size > max_len {
3002            bail!(
3003                "unsqueeze: maximum size for tensor at dimension {dim} is {max_len} but size is {size}"
3004            )
3005        }
3006        sizes.push(size);
3007        strides.push(if self.dims().is_empty() {
3008            1
3009        } else {
3010            strides[dim]
3011        });
3012
3013        if !self.dims().is_empty() {
3014            sizes[dim] = ((sizes[dim] as f32 - size as f32) / step as f32 + 1.) as usize;
3015            strides[dim] *= step;
3016        }
3017
3018        let tensor_ = Tensor_ {
3019            id: TensorId::new(),
3020            storage: self.storage.clone(),
3021            layout: Layout::new(sizes.into(), strides, self.layout.start_offset()),
3022            op: BackpropOp::new1(self, Op::Reshape),
3023            is_variable: false,
3024            dtype: self.dtype,
3025            device: self.device.clone(),
3026        };
3027        Ok(Tensor(Arc::new(tensor_)))
3028    }
3029}
3030
3031macro_rules! bin_trait {
3032    ($trait:ident, $fn1:ident, $mul:expr, $add:expr) => {
3033        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<B> for Tensor {
3034            type Output = Result<Tensor>;
3035
3036            fn $fn1(self, rhs: B) -> Self::Output {
3037                Tensor::$fn1(&self, rhs.borrow())
3038            }
3039        }
3040
3041        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<B> for &Tensor {
3042            type Output = Result<Tensor>;
3043
3044            fn $fn1(self, rhs: B) -> Self::Output {
3045                Tensor::$fn1(&self, rhs.borrow())
3046            }
3047        }
3048
3049        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<Tensor> for Result<B> {
3050            type Output = Result<Tensor>;
3051
3052            fn $fn1(self, rhs: Tensor) -> Self::Output {
3053                Tensor::$fn1(self?.borrow(), &rhs)
3054            }
3055        }
3056
3057        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<&Tensor> for Result<B> {
3058            type Output = Result<Tensor>;
3059
3060            fn $fn1(self, rhs: &Tensor) -> Self::Output {
3061                Tensor::$fn1(self?.borrow(), rhs)
3062            }
3063        }
3064
3065        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<Result<B>> for Tensor {
3066            type Output = Result<Tensor>;
3067
3068            fn $fn1(self, rhs: Result<B>) -> Self::Output {
3069                Tensor::$fn1(&self, rhs?.borrow())
3070            }
3071        }
3072
3073        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<Result<B>> for &Tensor {
3074            type Output = Result<Tensor>;
3075
3076            fn $fn1(self, rhs: Result<B>) -> Self::Output {
3077                Tensor::$fn1(&self, rhs?.borrow())
3078            }
3079        }
3080
3081        impl std::ops::$trait<f64> for Tensor {
3082            type Output = Result<Tensor>;
3083
3084            fn $fn1(self, rhs: f64) -> Self::Output {
3085                self.affine($mul(rhs), $add(rhs))
3086            }
3087        }
3088
3089        impl std::ops::$trait<f64> for &Tensor {
3090            type Output = Result<Tensor>;
3091
3092            fn $fn1(self, rhs: f64) -> Self::Output {
3093                self.affine($mul(rhs), $add(rhs))
3094            }
3095        }
3096    };
3097}
3098
3099bin_trait!(Add, add, |_| 1., |v| v);
3100bin_trait!(Sub, sub, |_| 1., |v: f64| -v);
3101bin_trait!(Mul, mul, |v| v, |_| 0.);
3102bin_trait!(Div, div, |v| 1. / v, |_| 0.);
3103
3104impl std::ops::Add<Tensor> for f64 {
3105    type Output = Result<Tensor>;
3106
3107    fn add(self, rhs: Tensor) -> Self::Output {
3108        rhs + self
3109    }
3110}
3111
3112impl std::ops::Add<&Tensor> for f64 {
3113    type Output = Result<Tensor>;
3114
3115    fn add(self, rhs: &Tensor) -> Self::Output {
3116        rhs + self
3117    }
3118}
3119
3120impl std::ops::Mul<Tensor> for f64 {
3121    type Output = Result<Tensor>;
3122
3123    fn mul(self, rhs: Tensor) -> Self::Output {
3124        rhs * self
3125    }
3126}
3127
3128impl std::ops::Mul<&Tensor> for f64 {
3129    type Output = Result<Tensor>;
3130
3131    fn mul(self, rhs: &Tensor) -> Self::Output {
3132        rhs * self
3133    }
3134}
3135
3136impl std::ops::Sub<Tensor> for f64 {
3137    type Output = Result<Tensor>;
3138
3139    fn sub(self, rhs: Tensor) -> Self::Output {
3140        rhs.affine(-1., self)
3141    }
3142}
3143
3144impl std::ops::Sub<&Tensor> for f64 {
3145    type Output = Result<Tensor>;
3146
3147    fn sub(self, rhs: &Tensor) -> Self::Output {
3148        rhs.affine(-1., self)
3149    }
3150}
3151
3152impl std::ops::Div<Tensor> for f64 {
3153    type Output = Result<Tensor>;
3154
3155    #[allow(clippy::suspicious_arithmetic_impl)]
3156    fn div(self, rhs: Tensor) -> Self::Output {
3157        rhs.recip()? * self
3158    }
3159}
3160
3161impl std::ops::Div<&Tensor> for f64 {
3162    type Output = Result<Tensor>;
3163
3164    #[allow(clippy::suspicious_arithmetic_impl)]
3165    fn div(self, rhs: &Tensor) -> Self::Output {
3166        rhs.recip()? * self
3167    }
3168}
3169
3170impl<S: Into<Shape>> From<(Storage, S)> for Tensor {
3171    fn from((storage, shape): (Storage, S)) -> Self {
3172        from_storage(storage, shape, BackpropOp::none(), false)
3173    }
3174}