Skip to main content

tenferro_tensor_core/
lib.rs

1//! Lightweight host tensor data model and metadata-only views.
2//!
3//! `tenferro-tensor-core` owns backend-independent tensor metadata and
4//! host-resident contiguous tensor storage. It does not own execution backends,
5//! backend buffers, GPU handles, provider selection, or materializing kernels.
6//! Runtime/backend-capable `TypedTensor<T, R>` lives in `tenferro-tensor`.
7//! This crate exposes rank/layout metadata plus host-only tensor adapters.
8//!
9//! # Examples
10//!
11//! ```rust
12//! use tenferro_tensor_core::{HostTensor, Rank, SliceSpec, TensorLayout};
13//!
14//! let tensor = HostTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0])?;
15//! let view = tensor
16//!     .as_view()
17//!     .slice_view(&[
18//!         SliceSpec { start: 0, end: 2, step: 1 },
19//!         SliceSpec { start: 1, end: 3, step: 1 },
20//!     ])?;
21//!
22//! assert_eq!(view.shape(), &[2, 2]);
23//! assert_eq!(view.as_slice()?, &[3.0, 4.0, 5.0, 6.0]);
24//!
25//! let layout = TensorLayout::<Rank<2>>::compact([2, 3])?;
26//! let transposed = layout.transpose_view([1, 0])?;
27//! assert_eq!(transposed.shape(), &[3, 2]);
28//! # Ok::<(), tenferro_tensor_core::Error>(())
29//! ```
30
31use num_complex::{Complex32, Complex64};
32use smallvec::SmallVec;
33
34mod layout;
35mod rank;
36
37pub use layout::TensorLayout;
38pub use rank::{DynRank, Rank, TensorRank};
39
40/// Small tensor shape vector with inline capacity for common dynamic ranks.
41///
42/// # Examples
43///
44/// ```rust
45/// use tenferro_tensor_core::ShapeVec;
46///
47/// let shape = ShapeVec::from_vec(vec![2, 3]);
48/// assert_eq!(shape.as_slice(), &[2, 3]);
49/// ```
50pub type ShapeVec = SmallVec<[usize; 8]>;
51
52/// Small tensor stride vector with signed element strides.
53///
54/// # Examples
55///
56/// ```rust
57/// use tenferro_tensor_core::StrideVec;
58///
59/// let strides = StrideVec::from_vec(vec![1, 2]);
60/// assert_eq!(strides.as_slice(), &[1, 2]);
61/// ```
62pub type StrideVec = SmallVec<[isize; 8]>;
63
64/// Result type for tensor data-model operations.
65///
66/// # Examples
67///
68/// ```rust
69/// use tenferro_tensor_core::{Error, Result};
70///
71/// let result: Result<()> = Err(Error::RankMismatch { expected: 2, actual: 1 });
72/// assert!(result.is_err());
73/// ```
74pub type Result<T> = std::result::Result<T, Error>;
75
76/// Data-model validation errors.
77///
78/// # Examples
79///
80/// ```rust
81/// use tenferro_tensor_core::Error;
82///
83/// let err = Error::ReshapeElementCountMismatch { from: 4, to: 5 };
84/// assert!(err.to_string().contains("reshape"));
85/// ```
86#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
87pub enum Error {
88    #[error("shape product {expected} does not match data length {actual}")]
89    ShapeDataLengthMismatch { expected: usize, actual: usize },
90    #[error("rank mismatch: expected {expected}, actual {actual}")]
91    RankMismatch { expected: usize, actual: usize },
92    #[error("axis {axis} out of bounds for rank {rank}")]
93    AxisOutOfBounds { axis: usize, rank: usize },
94    #[error("duplicate axis {axis} in permutation")]
95    DuplicateAxis { axis: usize },
96    #[error("invalid permutation length: expected {expected}, actual {actual}")]
97    InvalidPermutationLength { expected: usize, actual: usize },
98    #[error("invalid slice step {step}; zero is invalid and this API may require a positive step")]
99    InvalidSliceStep { step: isize },
100    #[error(
101        "slice bounds are invalid or unsupported: start={start}, end={end}, axis_len={axis_len}"
102    )]
103    InvalidSliceBounds {
104        start: isize,
105        end: isize,
106        axis_len: usize,
107    },
108    #[error("reshape element-count mismatch: from {from} to {to}")]
109    ReshapeElementCountMismatch { from: usize, to: usize },
110    #[error("view is not slice-contiguous")]
111    NonContiguousViewAsSlice,
112    #[error("dtype mismatch: expected {expected:?}, actual {actual:?}")]
113    DTypeMismatch { expected: DType, actual: DType },
114    #[error("view metadata is out of borrowed-slice bounds")]
115    ViewOutOfBounds,
116    /// Mutable layout metadata may alias the same physical element.
117    #[error("mutable tensor layout may overlap physical elements")]
118    OverlappingMutableLayout,
119    #[error("integer overflow while validating tensor metadata")]
120    IntegerOverflow,
121}
122
123/// Runtime scalar dtype tag.
124///
125/// # Examples
126///
127/// ```rust
128/// use tenferro_tensor_core::DType;
129///
130/// assert_eq!(DType::F64, DType::F64);
131/// ```
132#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
133pub enum DType {
134    F32,
135    F64,
136    I32,
137    I64,
138    Bool,
139    C32,
140    C64,
141}
142
143/// Sealed trait for scalar types supported by the core tensor data model.
144///
145/// # Examples
146///
147/// ```rust
148/// use tenferro_tensor_core::{DType, TensorScalar};
149///
150/// assert_eq!(f64::dtype(), DType::F64);
151/// assert_eq!(num_complex::Complex64::dtype(), DType::C64);
152/// ```
153pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
154    /// Real-valued counterpart of this scalar type.
155    type Real: TensorScalar;
156
157    /// Return the scalar dtype tag.
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use tenferro_tensor_core::{DType, TensorScalar};
163    ///
164    /// assert_eq!(i64::dtype(), DType::I64);
165    /// ```
166    fn dtype() -> DType;
167
168    fn into_tensor(shape: ShapeVec, data: Vec<Self>) -> Tensor;
169    fn tensor_slice(tensor: &Tensor) -> Option<&[Self]>;
170    fn tensor_mut_slice(tensor: &mut Tensor) -> Option<&mut [Self]>;
171    fn into_typed(tensor: Tensor) -> Option<HostTensor<Self>>;
172}
173
174mod private {
175    pub trait Sealed {}
176
177    impl Sealed for f32 {}
178    impl Sealed for f64 {}
179    impl Sealed for i32 {}
180    impl Sealed for i64 {}
181    impl Sealed for bool {}
182    impl Sealed for num_complex::Complex32 {}
183    impl Sealed for num_complex::Complex64 {}
184}
185
186macro_rules! impl_scalar {
187    ($ty:ty, $real:ty, $dtype:expr, $variant:ident) => {
188        impl TensorScalar for $ty {
189            type Real = $real;
190
191            fn dtype() -> DType {
192                $dtype
193            }
194
195            fn into_tensor(shape: ShapeVec, data: Vec<Self>) -> Tensor {
196                Tensor::$variant(HostTensor { data, shape })
197            }
198
199            fn tensor_slice(tensor: &Tensor) -> Option<&[Self]> {
200                match tensor {
201                    Tensor::$variant(typed) => Some(typed.as_slice()),
202                    _ => None,
203                }
204            }
205
206            fn tensor_mut_slice(tensor: &mut Tensor) -> Option<&mut [Self]> {
207                match tensor {
208                    Tensor::$variant(typed) => Some(typed.as_mut_slice()),
209                    _ => None,
210                }
211            }
212
213            fn into_typed(tensor: Tensor) -> Option<HostTensor<Self>> {
214                match tensor {
215                    Tensor::$variant(typed) => Some(typed),
216                    _ => None,
217                }
218            }
219        }
220    };
221}
222
223impl_scalar!(f32, f32, DType::F32, F32);
224impl_scalar!(f64, f64, DType::F64, F64);
225impl_scalar!(i32, i32, DType::I32, I32);
226impl_scalar!(i64, i64, DType::I64, I64);
227impl_scalar!(bool, bool, DType::Bool, Bool);
228impl_scalar!(Complex32, f32, DType::C32, C32);
229impl_scalar!(Complex64, f64, DType::C64, C64);
230
231/// Explicit slice descriptor.
232///
233/// A zero step is invalid. Layout metadata APIs support signed steps when
234/// reachable-range validation proves the view stays inside the backing
235/// allocation.
236///
237/// # Examples
238///
239/// ```rust
240/// use tenferro_tensor_core::SliceSpec;
241///
242/// let spec = SliceSpec { start: 1, end: 4, step: 2 };
243/// assert_eq!(spec.step, 2);
244/// ```
245#[derive(Clone, Copy, Debug, PartialEq, Eq)]
246pub struct SliceSpec {
247    pub start: isize,
248    pub end: isize,
249    pub step: isize,
250}
251
252/// Owned contiguous host tensor in column-major order.
253///
254/// # Examples
255///
256/// ```rust
257/// use tenferro_tensor_core::HostTensor;
258///
259/// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
260/// assert_eq!(tensor.as_slice(), &[1.0, 2.0]);
261/// # Ok::<(), tenferro_tensor_core::Error>(())
262/// ```
263#[derive(Clone, Debug, PartialEq)]
264pub struct HostTensor<T> {
265    data: Vec<T>,
266    shape: ShapeVec,
267}
268
269/// Dynamic owned host tensor over the supported dtype set.
270///
271/// # Examples
272///
273/// ```rust
274/// use tenferro_tensor_core::{DType, Tensor};
275///
276/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
277/// assert_eq!(tensor.dtype(), DType::F64);
278/// # Ok::<(), tenferro_tensor_core::Error>(())
279/// ```
280#[derive(Clone, Debug, PartialEq)]
281pub enum Tensor {
282    F32(HostTensor<f32>),
283    F64(HostTensor<f64>),
284    I32(HostTensor<i32>),
285    I64(HostTensor<i64>),
286    Bool(HostTensor<bool>),
287    C32(HostTensor<Complex32>),
288    C64(HostTensor<Complex64>),
289}
290
291/// Borrowed host tensor view with shape, strides, and offset metadata.
292///
293/// This type intentionally does not implement `PartialEq` because view
294/// equality is ambiguous between metadata identity, storage identity, and
295/// logical element equality.
296///
297/// # Examples
298///
299/// ```rust
300/// use tenferro_tensor_core::HostTensor;
301///
302/// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
303/// let view = tensor.as_view();
304/// assert_eq!(view.shape(), &[2]);
305/// # Ok::<(), tenferro_tensor_core::Error>(())
306/// ```
307///
308/// ```compile_fail
309/// # use tenferro_tensor_core::HostTensor;
310/// # let tensor = HostTensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
311/// let a = tensor.as_view();
312/// let b = tensor.as_view();
313/// let _ = a == b;
314/// ```
315#[derive(Clone, Debug)]
316pub struct HostTensorView<'a, T> {
317    data: &'a [T],
318    shape: ShapeVec,
319    strides: StrideVec,
320    offset: isize,
321}
322
323/// Dynamic borrowed host tensor view.
324///
325/// # Examples
326///
327/// ```rust
328/// use tenferro_tensor_core::{DType, Tensor};
329///
330/// let tensor = Tensor::from_vec_col_major(vec![1], vec![true])?;
331/// let view = tensor.as_view();
332/// assert_eq!(view.dtype(), DType::Bool);
333/// # Ok::<(), tenferro_tensor_core::Error>(())
334/// ```
335///
336/// ```compile_fail
337/// # use tenferro_tensor_core::Tensor;
338/// # let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
339/// let a = tensor.as_view();
340/// let b = tensor.as_view();
341/// let _ = a == b;
342/// ```
343#[derive(Clone, Debug)]
344pub enum TensorView<'a> {
345    F32(HostTensorView<'a, f32>),
346    F64(HostTensorView<'a, f64>),
347    I32(HostTensorView<'a, i32>),
348    I64(HostTensorView<'a, i64>),
349    Bool(HostTensorView<'a, bool>),
350    C32(HostTensorView<'a, Complex32>),
351    C64(HostTensorView<'a, Complex64>),
352}
353
354/// Core-neutral tensor input reference.
355///
356/// # Examples
357///
358/// ```rust
359/// use tenferro_tensor_core::{Tensor, TensorRef};
360///
361/// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f32])?;
362/// let reference = TensorRef::Tensor(&tensor);
363/// assert_eq!(reference.shape(), &[1]);
364/// # Ok::<(), tenferro_tensor_core::Error>(())
365/// ```
366#[derive(Clone, Debug)]
367pub enum TensorRef<'a> {
368    Tensor(&'a Tensor),
369    View(TensorView<'a>),
370}
371
372fn checked_product(shape: &[usize]) -> Result<usize> {
373    shape.iter().try_fold(1usize, |acc, &dim| {
374        acc.checked_mul(dim).ok_or(Error::IntegerOverflow)
375    })
376}
377
378fn checked_logical_element_count(shape: &[usize]) -> Result<usize> {
379    if shape.contains(&0) {
380        return Ok(0);
381    }
382    checked_product(shape)
383}
384
385fn checked_shape_len(shape: &[usize], data_len: usize) -> Result<usize> {
386    validate_shape_metadata(shape)?;
387    let expected = checked_product(shape)?;
388    if expected != data_len {
389        return Err(Error::ShapeDataLengthMismatch {
390            expected,
391            actual: data_len,
392        });
393    }
394    Ok(expected)
395}
396
397fn validate_shape_metadata(shape: &[usize]) -> Result<()> {
398    checked_product(shape)?;
399    col_major_strides(shape)?;
400    Ok(())
401}
402
403fn compact_col_major_strides(shape: &[usize]) -> StrideVec {
404    // Invariant: HostTensor constructors validate shape metadata before as_view can call this.
405    col_major_strides(shape).expect("HostTensor shape metadata is validated at construction")
406}
407
408/// Return compact column-major strides for a shape.
409///
410/// # Examples
411///
412/// ```rust
413/// use tenferro_tensor_core::col_major_strides;
414///
415/// assert_eq!(col_major_strides(&[2, 3])?.as_slice(), &[1, 2]);
416/// # Ok::<(), tenferro_tensor_core::Error>(())
417/// ```
418pub fn col_major_strides(shape: &[usize]) -> Result<StrideVec> {
419    let mut strides = StrideVec::new();
420    let mut stride = 1isize;
421    for &extent in shape {
422        strides.push(stride);
423        let extent = isize::try_from(extent).map_err(|_| Error::IntegerOverflow)?;
424        stride = stride.checked_mul(extent).ok_or(Error::IntegerOverflow)?;
425    }
426    Ok(strides)
427}
428
429fn validate_permutation(rank: usize, axes: &[usize]) -> Result<()> {
430    if axes.len() != rank {
431        return Err(Error::InvalidPermutationLength {
432            expected: rank,
433            actual: axes.len(),
434        });
435    }
436    let mut seen = vec![false; rank];
437    for &axis in axes {
438        if axis >= rank {
439            return Err(Error::AxisOutOfBounds { axis, rank });
440        }
441        if seen[axis] {
442            return Err(Error::DuplicateAxis { axis });
443        }
444        seen[axis] = true;
445    }
446    Ok(())
447}
448
449fn validate_view_bounds<T>(
450    data: &[T],
451    shape: &[usize],
452    strides: &[isize],
453    offset: isize,
454) -> Result<()> {
455    checked_logical_element_count(shape)?;
456    layout::validate_reachable_bounds(shape, strides, offset, data.len())
457}
458
459fn is_slice_contiguous(shape: &[usize], strides: &[isize]) -> Result<bool> {
460    if shape.contains(&0) {
461        // Empty logical views do not touch storage, so arbitrary strides are
462        // indistinguishable from compact strides for slice/reshape purposes.
463        return Ok(true);
464    }
465
466    let mut expected = 1isize;
467    for (&extent, &stride) in shape.iter().zip(strides) {
468        if extent <= 1 {
469            continue;
470        }
471        if stride != expected {
472            return Ok(false);
473        }
474        let extent = isize::try_from(extent).map_err(|_| Error::IntegerOverflow)?;
475        let next = expected.checked_mul(extent).ok_or(Error::IntegerOverflow)?;
476        expected = next;
477    }
478    Ok(true)
479}
480
481impl<T> HostTensor<T> {
482    /// Create an owned tensor from a column-major host buffer.
483    ///
484    /// # Examples
485    ///
486    /// ```rust
487    /// use tenferro_tensor_core::HostTensor;
488    ///
489    /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1_i64, 2])?;
490    /// assert_eq!(tensor.shape(), &[2]);
491    /// # Ok::<(), tenferro_tensor_core::Error>(())
492    /// ```
493    pub fn from_vec_col_major(shape: impl Into<ShapeVec>, data: Vec<T>) -> Result<Self> {
494        let shape = shape.into();
495        checked_shape_len(&shape, data.len())?;
496        Ok(Self { data, shape })
497    }
498
499    /// Borrow this tensor's shape.
500    ///
501    /// # Examples
502    ///
503    /// ```rust
504    /// use tenferro_tensor_core::HostTensor;
505    ///
506    /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![true, false])?;
507    /// assert_eq!(tensor.shape(), &[2]);
508    /// # Ok::<(), tenferro_tensor_core::Error>(())
509    /// ```
510    pub fn shape(&self) -> &[usize] {
511        &self.shape
512    }
513
514    /// Return the tensor rank.
515    ///
516    /// # Examples
517    ///
518    /// ```rust
519    /// use tenferro_tensor_core::HostTensor;
520    ///
521    /// let tensor = HostTensor::from_vec_col_major(vec![2, 1], vec![1.0_f32, 2.0])?;
522    /// assert_eq!(tensor.rank(), 2);
523    /// # Ok::<(), tenferro_tensor_core::Error>(())
524    /// ```
525    pub fn rank(&self) -> usize {
526        self.shape.len()
527    }
528
529    /// Returns `true` when this tensor has zero elements.
530    ///
531    /// # Examples
532    ///
533    /// ```rust
534    /// use tenferro_tensor_core::HostTensor;
535    ///
536    /// let tensor = HostTensor::<f64>::from_vec_col_major(vec![0], vec![])?;
537    /// assert!(tensor.is_empty());
538    /// # Ok::<(), tenferro_tensor_core::Error>(())
539    /// ```
540    pub fn is_empty(&self) -> bool {
541        self.data.is_empty()
542    }
543
544    /// Borrow the contiguous column-major host buffer.
545    ///
546    /// # Examples
547    ///
548    /// ```rust
549    /// use tenferro_tensor_core::HostTensor;
550    ///
551    /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![7_i32])?;
552    /// assert_eq!(tensor.as_slice(), &[7]);
553    /// # Ok::<(), tenferro_tensor_core::Error>(())
554    /// ```
555    pub fn as_slice(&self) -> &[T] {
556        &self.data
557    }
558
559    /// Mutably borrow the contiguous column-major host buffer.
560    ///
561    /// # Examples
562    ///
563    /// ```rust
564    /// use tenferro_tensor_core::HostTensor;
565    ///
566    /// let mut tensor = HostTensor::from_vec_col_major(vec![1], vec![7_i32])?;
567    /// tensor.as_mut_slice()[0] = 8;
568    /// assert_eq!(tensor.as_slice(), &[8]);
569    /// # Ok::<(), tenferro_tensor_core::Error>(())
570    /// ```
571    pub fn as_mut_slice(&mut self) -> &mut [T] {
572        &mut self.data
573    }
574
575    /// Borrow this tensor as a compact zero-offset view.
576    ///
577    /// # Examples
578    ///
579    /// ```rust
580    /// use tenferro_tensor_core::HostTensor;
581    ///
582    /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
583    /// assert!(tensor.as_view().is_zero_offset_col_major()?);
584    /// # Ok::<(), tenferro_tensor_core::Error>(())
585    /// ```
586    pub fn as_view(&self) -> HostTensorView<'_, T> {
587        HostTensorView {
588            data: &self.data,
589            shape: self.shape.clone(),
590            strides: compact_col_major_strides(&self.shape),
591            offset: 0,
592        }
593    }
594
595    /// Consume this tensor into its shape and column-major buffer.
596    ///
597    /// # Examples
598    ///
599    /// ```rust
600    /// use tenferro_tensor_core::HostTensor;
601    ///
602    /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![3.0_f64])?;
603    /// assert_eq!(tensor.into_vec_col_major().1, vec![3.0]);
604    /// # Ok::<(), tenferro_tensor_core::Error>(())
605    /// ```
606    pub fn into_vec_col_major(self) -> (ShapeVec, Vec<T>) {
607        (self.shape, self.data)
608    }
609
610    /// Consume this tensor into the same data with a different shape.
611    ///
612    /// # Examples
613    ///
614    /// ```rust
615    /// use tenferro_tensor_core::HostTensor;
616    ///
617    /// let tensor = HostTensor::from_vec_col_major(vec![4], vec![1.0_f64, 2.0, 3.0, 4.0])?;
618    /// assert_eq!(tensor.into_reshaped(vec![2, 2])?.shape(), &[2, 2]);
619    /// # Ok::<(), tenferro_tensor_core::Error>(())
620    /// ```
621    pub fn into_reshaped(self, shape: impl Into<ShapeVec>) -> Result<Self> {
622        let shape = shape.into();
623        let from = self.data.len();
624        let to = checked_product(&shape)?;
625        if from != to {
626            return Err(Error::ReshapeElementCountMismatch { from, to });
627        }
628        validate_shape_metadata(&shape)?;
629        Ok(Self {
630            data: self.data,
631            shape,
632        })
633    }
634}
635
636impl<'a, T> HostTensorView<'a, T> {
637    /// Create a typed view from explicit metadata and validate bounds eagerly.
638    ///
639    /// # Examples
640    ///
641    /// ```rust
642    /// use tenferro_tensor_core::HostTensorView;
643    ///
644    /// let data = [1.0_f64, 2.0, 3.0, 4.0];
645    /// let view = HostTensorView::from_slice(vec![2], vec![1], 1, &data)?;
646    /// assert_eq!(view.as_slice()?, &[2.0, 3.0]);
647    /// # Ok::<(), tenferro_tensor_core::Error>(())
648    /// ```
649    pub fn from_slice(
650        shape: impl Into<ShapeVec>,
651        strides: impl Into<StrideVec>,
652        offset: isize,
653        data: &'a [T],
654    ) -> Result<Self> {
655        let shape = shape.into();
656        let strides = strides.into();
657        validate_view_bounds(data, &shape, &strides, offset)?;
658        Ok(Self {
659            data,
660            shape,
661            strides,
662            offset,
663        })
664    }
665
666    /// Borrow this view's shape.
667    ///
668    /// # Examples
669    ///
670    /// ```rust
671    /// use tenferro_tensor_core::HostTensor;
672    ///
673    /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
674    /// assert_eq!(tensor.as_view().shape(), &[2]);
675    /// # Ok::<(), tenferro_tensor_core::Error>(())
676    /// ```
677    pub fn shape(&self) -> &[usize] {
678        &self.shape
679    }
680
681    /// Borrow this view's signed element strides.
682    ///
683    /// # Examples
684    ///
685    /// ```rust
686    /// use tenferro_tensor_core::HostTensor;
687    ///
688    /// let tensor = HostTensor::from_vec_col_major(vec![2, 3], vec![0_i32; 6])?;
689    /// assert_eq!(tensor.as_view().strides(), &[1, 2]);
690    /// # Ok::<(), tenferro_tensor_core::Error>(())
691    /// ```
692    pub fn strides(&self) -> &[isize] {
693        &self.strides
694    }
695
696    /// Return this view's signed element offset into the backing slice.
697    ///
698    /// # Examples
699    ///
700    /// ```rust
701    /// use tenferro_tensor_core::HostTensor;
702    ///
703    /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![true])?;
704    /// assert_eq!(tensor.as_view().offset(), 0);
705    /// # Ok::<(), tenferro_tensor_core::Error>(())
706    /// ```
707    pub fn offset(&self) -> isize {
708        self.offset
709    }
710
711    /// Return the view rank.
712    ///
713    /// # Examples
714    ///
715    /// ```rust
716    /// use tenferro_tensor_core::HostTensor;
717    ///
718    /// let tensor = HostTensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
719    /// assert_eq!(tensor.as_view().rank(), 2);
720    /// # Ok::<(), tenferro_tensor_core::Error>(())
721    /// ```
722    pub fn rank(&self) -> usize {
723        self.shape.len()
724    }
725
726    /// Returns `true` when this view has zero logical elements.
727    ///
728    /// # Examples
729    ///
730    /// ```rust
731    /// use tenferro_tensor_core::HostTensorView;
732    ///
733    /// let data = [1.0_f64];
734    /// let view = HostTensorView::from_slice(vec![0], vec![1], 0, &data)?;
735    /// assert!(view.is_empty());
736    /// # Ok::<(), tenferro_tensor_core::Error>(())
737    /// ```
738    pub fn is_empty(&self) -> bool {
739        self.shape.contains(&0)
740    }
741
742    /// Return whether this view has compact column-major logical strides.
743    ///
744    /// # Examples
745    ///
746    /// ```rust
747    /// use tenferro_tensor_core::HostTensor;
748    ///
749    /// let tensor = HostTensor::from_vec_col_major(vec![2, 2], vec![0_i32; 4])?;
750    /// assert!(tensor.as_view().is_compact_col_major()?);
751    /// # Ok::<(), tenferro_tensor_core::Error>(())
752    /// ```
753    pub fn is_compact_col_major(&self) -> Result<bool> {
754        is_slice_contiguous(&self.shape, &self.strides)
755    }
756
757    /// Return whether this view is compact column-major and starts at offset zero.
758    ///
759    /// # Examples
760    ///
761    /// ```rust
762    /// use tenferro_tensor_core::HostTensor;
763    ///
764    /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![1_i64])?;
765    /// assert!(tensor.as_view().is_zero_offset_col_major()?);
766    /// # Ok::<(), tenferro_tensor_core::Error>(())
767    /// ```
768    pub fn is_zero_offset_col_major(&self) -> Result<bool> {
769        Ok(self.offset == 0 && self.is_compact_col_major()?)
770    }
771
772    /// Borrow the slice-contiguous backing region for this view.
773    ///
774    /// # Examples
775    ///
776    /// ```rust
777    /// use tenferro_tensor_core::HostTensorView;
778    ///
779    /// let data = [1_i32, 2, 3, 4];
780    /// let view = HostTensorView::from_slice(vec![2], vec![1], 1, &data)?;
781    /// assert_eq!(view.as_slice()?, &[2, 3]);
782    /// # Ok::<(), tenferro_tensor_core::Error>(())
783    /// ```
784    pub fn as_slice(&self) -> Result<&'a [T]> {
785        if !is_slice_contiguous(&self.shape, &self.strides)? {
786            return Err(Error::NonContiguousViewAsSlice);
787        }
788        let len = checked_product(&self.shape)?;
789        let start = usize::try_from(self.offset).map_err(|_| Error::IntegerOverflow)?;
790        let end = start.checked_add(len).ok_or(Error::IntegerOverflow)?;
791        self.data.get(start..end).ok_or(Error::ViewOutOfBounds)
792    }
793
794    /// Return a metadata-only reshape of this compact column-major view.
795    ///
796    /// # Examples
797    ///
798    /// ```rust
799    /// use tenferro_tensor_core::HostTensor;
800    ///
801    /// let tensor = HostTensor::from_vec_col_major(vec![4], vec![1.0_f64, 2.0, 3.0, 4.0])?;
802    /// assert_eq!(tensor.as_view().reshape_view(vec![2, 2])?.shape(), &[2, 2]);
803    /// # Ok::<(), tenferro_tensor_core::Error>(())
804    /// ```
805    pub fn reshape_view(&self, shape: impl Into<ShapeVec>) -> Result<Self> {
806        if !self.is_compact_col_major()? {
807            return Err(Error::NonContiguousViewAsSlice);
808        }
809        let shape = shape.into();
810        let from = checked_product(&self.shape)?;
811        let to = checked_product(&shape)?;
812        if from != to {
813            return Err(Error::ReshapeElementCountMismatch { from, to });
814        }
815        Self::from_slice(
816            shape.clone(),
817            col_major_strides(&shape)?,
818            self.offset,
819            self.data,
820        )
821    }
822
823    /// Return a metadata-only transposed view with axes in the requested order.
824    ///
825    /// # Examples
826    ///
827    /// ```rust
828    /// use tenferro_tensor_core::HostTensor;
829    ///
830    /// let tensor = HostTensor::from_vec_col_major(vec![2, 3], vec![0_i32; 6])?;
831    /// let view = tensor.as_view().transpose_view(&[1, 0])?;
832    /// assert_eq!(view.shape(), &[3, 2]);
833    /// assert_eq!(view.strides(), &[2, 1]);
834    /// # Ok::<(), tenferro_tensor_core::Error>(())
835    /// ```
836    pub fn transpose_view(&self, axes: &[usize]) -> Result<Self> {
837        validate_permutation(self.rank(), axes)?;
838        let shape = axes
839            .iter()
840            .map(|&axis| self.shape[axis])
841            .collect::<ShapeVec>();
842        let strides = axes
843            .iter()
844            .map(|&axis| self.strides[axis])
845            .collect::<StrideVec>();
846        Self::from_slice(shape, strides, self.offset, self.data)
847    }
848
849    /// Return a metadata-only positive-step slice of this view.
850    ///
851    /// # Examples
852    ///
853    /// ```rust
854    /// use tenferro_tensor_core::{SliceSpec, HostTensor};
855    ///
856    /// let tensor = HostTensor::from_vec_col_major(vec![4], vec![1_i64, 2, 3, 4])?;
857    /// let view = tensor
858    ///     .as_view()
859    ///     .slice_view(&[SliceSpec { start: 1, end: 4, step: 2 }])?;
860    /// assert_eq!(view.shape(), &[2]);
861    /// # Ok::<(), tenferro_tensor_core::Error>(())
862    /// ```
863    pub fn slice_view(&self, spec: &[SliceSpec]) -> Result<Self> {
864        if spec.len() != self.rank() {
865            return Err(Error::RankMismatch {
866                expected: self.rank(),
867                actual: spec.len(),
868            });
869        }
870        let mut shape = ShapeVec::new();
871        let mut strides = StrideVec::new();
872        let mut offset = self.offset;
873        for ((&axis_len, &stride), slice) in self.shape.iter().zip(self.strides.iter()).zip(spec) {
874            if slice.step <= 0 {
875                return Err(Error::InvalidSliceStep { step: slice.step });
876            }
877            if slice.start < 0 || slice.end < 0 {
878                return Err(Error::InvalidSliceBounds {
879                    start: slice.start,
880                    end: slice.end,
881                    axis_len,
882                });
883            }
884            let start = usize::try_from(slice.start).map_err(|_| Error::IntegerOverflow)?;
885            let end = usize::try_from(slice.end).map_err(|_| Error::IntegerOverflow)?;
886            if start > axis_len || end > axis_len {
887                return Err(Error::InvalidSliceBounds {
888                    start: slice.start,
889                    end: slice.end,
890                    axis_len,
891                });
892            }
893            let step = usize::try_from(slice.step).map_err(|_| Error::IntegerOverflow)?;
894            let extent = if start >= end {
895                0
896            } else {
897                end.checked_sub(start)
898                    .and_then(|span| span.checked_add(step - 1))
899                    .ok_or(Error::IntegerOverflow)?
900                    / step
901            };
902            let start_offset = isize::try_from(start)
903                .map_err(|_| Error::IntegerOverflow)?
904                .checked_mul(stride)
905                .ok_or(Error::IntegerOverflow)?;
906            offset = offset
907                .checked_add(start_offset)
908                .ok_or(Error::IntegerOverflow)?;
909            let new_stride = stride
910                .checked_mul(slice.step)
911                .ok_or(Error::IntegerOverflow)?;
912            shape.push(extent);
913            strides.push(new_stride);
914        }
915        Self::from_slice(shape, strides, offset, self.data)
916    }
917}
918
919impl Tensor {
920    /// Create a dynamic tensor from a column-major host buffer.
921    ///
922    /// # Examples
923    ///
924    /// ```rust
925    /// use tenferro_tensor_core::{DType, Tensor};
926    ///
927    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f32])?;
928    /// assert_eq!(tensor.dtype(), DType::F32);
929    /// # Ok::<(), tenferro_tensor_core::Error>(())
930    /// ```
931    pub fn from_vec_col_major<T: TensorScalar>(
932        shape: impl Into<ShapeVec>,
933        data: Vec<T>,
934    ) -> Result<Self> {
935        let shape = shape.into();
936        checked_shape_len(&shape, data.len())?;
937        Ok(T::into_tensor(shape, data))
938    }
939
940    /// Return the tensor dtype tag.
941    ///
942    /// # Examples
943    ///
944    /// ```rust
945    /// use tenferro_tensor_core::{DType, Tensor};
946    ///
947    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![false])?;
948    /// assert_eq!(tensor.dtype(), DType::Bool);
949    /// # Ok::<(), tenferro_tensor_core::Error>(())
950    /// ```
951    pub fn dtype(&self) -> DType {
952        match self {
953            Self::F32(_) => DType::F32,
954            Self::F64(_) => DType::F64,
955            Self::I32(_) => DType::I32,
956            Self::I64(_) => DType::I64,
957            Self::Bool(_) => DType::Bool,
958            Self::C32(_) => DType::C32,
959            Self::C64(_) => DType::C64,
960        }
961    }
962
963    /// Borrow the tensor shape.
964    ///
965    /// # Examples
966    ///
967    /// ```rust
968    /// use tenferro_tensor_core::Tensor;
969    ///
970    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
971    /// assert_eq!(tensor.shape(), &[2]);
972    /// # Ok::<(), tenferro_tensor_core::Error>(())
973    /// ```
974    pub fn shape(&self) -> &[usize] {
975        match self {
976            Self::F32(t) => t.shape(),
977            Self::F64(t) => t.shape(),
978            Self::I32(t) => t.shape(),
979            Self::I64(t) => t.shape(),
980            Self::Bool(t) => t.shape(),
981            Self::C32(t) => t.shape(),
982            Self::C64(t) => t.shape(),
983        }
984    }
985
986    /// Return the tensor rank.
987    ///
988    /// # Examples
989    ///
990    /// ```rust
991    /// use tenferro_tensor_core::Tensor;
992    ///
993    /// let tensor = Tensor::from_vec_col_major(vec![1, 1], vec![1_i64])?;
994    /// assert_eq!(tensor.rank(), 2);
995    /// # Ok::<(), tenferro_tensor_core::Error>(())
996    /// ```
997    pub fn rank(&self) -> usize {
998        self.shape().len()
999    }
1000
1001    /// Return whether the tensor has zero elements.
1002    ///
1003    /// # Examples
1004    ///
1005    /// ```rust
1006    /// use tenferro_tensor_core::Tensor;
1007    ///
1008    /// let tensor = Tensor::from_vec_col_major(vec![0], Vec::<f64>::new())?;
1009    /// assert!(tensor.is_empty());
1010    /// # Ok::<(), tenferro_tensor_core::Error>(())
1011    /// ```
1012    pub fn is_empty(&self) -> bool {
1013        match self {
1014            Self::F32(t) => t.is_empty(),
1015            Self::F64(t) => t.is_empty(),
1016            Self::I32(t) => t.is_empty(),
1017            Self::I64(t) => t.is_empty(),
1018            Self::Bool(t) => t.is_empty(),
1019            Self::C32(t) => t.is_empty(),
1020            Self::C64(t) => t.is_empty(),
1021        }
1022    }
1023
1024    /// Borrow the typed host slice when the dtype matches.
1025    ///
1026    /// # Examples
1027    ///
1028    /// ```rust
1029    /// use tenferro_tensor_core::Tensor;
1030    ///
1031    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![3.0_f64])?;
1032    /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
1033    /// assert!(tensor.as_slice::<f32>().is_err());
1034    /// # Ok::<(), tenferro_tensor_core::Error>(())
1035    /// ```
1036    pub fn as_slice<T: TensorScalar>(&self) -> Result<&[T]> {
1037        T::tensor_slice(self).ok_or(Error::DTypeMismatch {
1038            expected: T::dtype(),
1039            actual: self.dtype(),
1040        })
1041    }
1042
1043    /// Mutably borrow the typed host slice when the dtype matches.
1044    ///
1045    /// # Examples
1046    ///
1047    /// ```rust
1048    /// use tenferro_tensor_core::Tensor;
1049    ///
1050    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![3.0_f64])?;
1051    /// tensor.as_mut_slice::<f64>()?[0] = 4.0;
1052    /// assert_eq!(tensor.as_slice::<f64>()?, &[4.0]);
1053    /// # Ok::<(), tenferro_tensor_core::Error>(())
1054    /// ```
1055    pub fn as_mut_slice<T: TensorScalar>(&mut self) -> Result<&mut [T]> {
1056        let actual = self.dtype();
1057        T::tensor_mut_slice(self).ok_or(Error::DTypeMismatch {
1058            expected: T::dtype(),
1059            actual,
1060        })
1061    }
1062
1063    /// Borrow this tensor as a dynamic zero-offset view.
1064    ///
1065    /// # Examples
1066    ///
1067    /// ```rust
1068    /// use tenferro_tensor_core::{DType, Tensor};
1069    ///
1070    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
1071    /// assert_eq!(tensor.as_view().dtype(), DType::I64);
1072    /// # Ok::<(), tenferro_tensor_core::Error>(())
1073    /// ```
1074    pub fn as_view(&self) -> TensorView<'_> {
1075        match self {
1076            Self::F32(t) => TensorView::F32(t.as_view()),
1077            Self::F64(t) => TensorView::F64(t.as_view()),
1078            Self::I32(t) => TensorView::I32(t.as_view()),
1079            Self::I64(t) => TensorView::I64(t.as_view()),
1080            Self::Bool(t) => TensorView::Bool(t.as_view()),
1081            Self::C32(t) => TensorView::C32(t.as_view()),
1082            Self::C64(t) => TensorView::C64(t.as_view()),
1083        }
1084    }
1085
1086    /// Consume this tensor and return typed column-major data when the dtype matches.
1087    ///
1088    /// # Examples
1089    ///
1090    /// ```rust
1091    /// use tenferro_tensor_core::Tensor;
1092    ///
1093    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f32])?;
1094    /// assert_eq!(tensor.into_vec_col_major::<f32>()?.1, vec![2.0]);
1095    /// # Ok::<(), tenferro_tensor_core::Error>(())
1096    /// ```
1097    pub fn into_vec_col_major<T: TensorScalar>(self) -> Result<(ShapeVec, Vec<T>)> {
1098        let actual = self.dtype();
1099        T::into_typed(self)
1100            .map(HostTensor::into_vec_col_major)
1101            .ok_or(Error::DTypeMismatch {
1102                expected: T::dtype(),
1103                actual,
1104            })
1105    }
1106}
1107
1108macro_rules! impl_dynamic_view {
1109    ($self:ident, $method:ident($($arg:ident),*) => $inner:ident) => {
1110        match $self {
1111            TensorView::F32(view) => TensorView::F32(view.$method($($arg),*)?),
1112            TensorView::F64(view) => TensorView::F64(view.$method($($arg),*)?),
1113            TensorView::I32(view) => TensorView::I32(view.$method($($arg),*)?),
1114            TensorView::I64(view) => TensorView::I64(view.$method($($arg),*)?),
1115            TensorView::Bool(view) => TensorView::Bool(view.$method($($arg),*)?),
1116            TensorView::C32(view) => TensorView::C32(view.$method($($arg),*)?),
1117            TensorView::C64(view) => TensorView::C64(view.$method($($arg),*)?),
1118        }
1119    };
1120}
1121
1122impl<'a> TensorView<'a> {
1123    /// Return this view's dtype.
1124    ///
1125    /// # Examples
1126    ///
1127    /// ```rust
1128    /// use tenferro_tensor_core::{DType, Tensor};
1129    ///
1130    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f32])?;
1131    /// assert_eq!(tensor.as_view().dtype(), DType::F32);
1132    /// # Ok::<(), tenferro_tensor_core::Error>(())
1133    /// ```
1134    pub fn dtype(&self) -> DType {
1135        match self {
1136            Self::F32(_) => DType::F32,
1137            Self::F64(_) => DType::F64,
1138            Self::I32(_) => DType::I32,
1139            Self::I64(_) => DType::I64,
1140            Self::Bool(_) => DType::Bool,
1141            Self::C32(_) => DType::C32,
1142            Self::C64(_) => DType::C64,
1143        }
1144    }
1145
1146    /// Borrow this view's shape.
1147    ///
1148    /// # Examples
1149    ///
1150    /// ```rust
1151    /// use tenferro_tensor_core::Tensor;
1152    ///
1153    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1154    /// assert_eq!(tensor.as_view().shape(), &[1]);
1155    /// # Ok::<(), tenferro_tensor_core::Error>(())
1156    /// ```
1157    pub fn shape(&self) -> &[usize] {
1158        match self {
1159            Self::F32(view) => view.shape(),
1160            Self::F64(view) => view.shape(),
1161            Self::I32(view) => view.shape(),
1162            Self::I64(view) => view.shape(),
1163            Self::Bool(view) => view.shape(),
1164            Self::C32(view) => view.shape(),
1165            Self::C64(view) => view.shape(),
1166        }
1167    }
1168
1169    /// Return the view rank.
1170    ///
1171    /// # Examples
1172    ///
1173    /// ```rust
1174    /// use tenferro_tensor_core::Tensor;
1175    ///
1176    /// let tensor = Tensor::from_vec_col_major(vec![1, 1], vec![1_i64])?;
1177    /// assert_eq!(tensor.as_view().rank(), 2);
1178    /// # Ok::<(), tenferro_tensor_core::Error>(())
1179    /// ```
1180    pub fn rank(&self) -> usize {
1181        self.shape().len()
1182    }
1183
1184    /// Return whether this view has zero logical elements.
1185    ///
1186    /// # Examples
1187    ///
1188    /// ```rust
1189    /// use tenferro_tensor_core::Tensor;
1190    ///
1191    /// let tensor = Tensor::from_vec_col_major(vec![0], Vec::<f64>::new())?;
1192    /// assert!(tensor.as_view().is_empty());
1193    /// # Ok::<(), tenferro_tensor_core::Error>(())
1194    /// ```
1195    pub fn is_empty(&self) -> bool {
1196        match self {
1197            Self::F32(view) => view.is_empty(),
1198            Self::F64(view) => view.is_empty(),
1199            Self::I32(view) => view.is_empty(),
1200            Self::I64(view) => view.is_empty(),
1201            Self::Bool(view) => view.is_empty(),
1202            Self::C32(view) => view.is_empty(),
1203            Self::C64(view) => view.is_empty(),
1204        }
1205    }
1206
1207    /// Return a metadata-only reshape of this dynamic view.
1208    ///
1209    /// # Examples
1210    ///
1211    /// ```rust
1212    /// use tenferro_tensor_core::Tensor;
1213    ///
1214    /// let tensor = Tensor::from_vec_col_major(vec![4], vec![1_i32, 2, 3, 4])?;
1215    /// assert_eq!(tensor.as_view().reshape_view(vec![2, 2])?.shape(), &[2, 2]);
1216    /// # Ok::<(), tenferro_tensor_core::Error>(())
1217    /// ```
1218    pub fn reshape_view(&self, shape: impl Into<ShapeVec>) -> Result<Self> {
1219        let shape = shape.into();
1220        Ok(impl_dynamic_view!(self, reshape_view(shape) => view))
1221    }
1222
1223    /// Return a metadata-only transposed dynamic view with axes in the requested order.
1224    ///
1225    /// # Examples
1226    ///
1227    /// ```rust
1228    /// use tenferro_tensor_core::Tensor;
1229    ///
1230    /// let tensor = Tensor::from_vec_col_major(vec![1, 2], vec![1_i64, 2])?;
1231    /// assert_eq!(tensor.as_view().transpose_view(&[1, 0])?.shape(), &[2, 1]);
1232    /// # Ok::<(), tenferro_tensor_core::Error>(())
1233    /// ```
1234    pub fn transpose_view(&self, axes: &[usize]) -> Result<Self> {
1235        Ok(impl_dynamic_view!(self, transpose_view(axes) => view))
1236    }
1237
1238    /// Return a metadata-only positive-step slice of this dynamic view.
1239    ///
1240    /// # Examples
1241    ///
1242    /// ```rust
1243    /// use tenferro_tensor_core::{SliceSpec, Tensor};
1244    ///
1245    /// let tensor = Tensor::from_vec_col_major(vec![3], vec![1_i64, 2, 3])?;
1246    /// assert_eq!(
1247    ///     tensor.as_view().slice_view(&[SliceSpec { start: 1, end: 3, step: 1 }])?.shape(),
1248    ///     &[2],
1249    /// );
1250    /// # Ok::<(), tenferro_tensor_core::Error>(())
1251    /// ```
1252    pub fn slice_view(&self, spec: &[SliceSpec]) -> Result<Self> {
1253        Ok(impl_dynamic_view!(self, slice_view(spec) => view))
1254    }
1255}
1256
1257impl<'a> TensorRef<'a> {
1258    /// Return the referenced dtype.
1259    ///
1260    /// # Examples
1261    ///
1262    /// ```rust
1263    /// use tenferro_tensor_core::{DType, Tensor, TensorRef};
1264    ///
1265    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
1266    /// assert_eq!(TensorRef::Tensor(&tensor).dtype(), DType::I64);
1267    /// # Ok::<(), tenferro_tensor_core::Error>(())
1268    /// ```
1269    pub fn dtype(&self) -> DType {
1270        match self {
1271            Self::Tensor(tensor) => tensor.dtype(),
1272            Self::View(view) => view.dtype(),
1273        }
1274    }
1275
1276    /// Borrow the referenced shape.
1277    ///
1278    /// # Examples
1279    ///
1280    /// ```rust
1281    /// use tenferro_tensor_core::{Tensor, TensorRef};
1282    ///
1283    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
1284    /// assert_eq!(TensorRef::Tensor(&tensor).shape(), &[1]);
1285    /// # Ok::<(), tenferro_tensor_core::Error>(())
1286    /// ```
1287    pub fn shape(&self) -> &[usize] {
1288        match self {
1289            Self::Tensor(tensor) => tensor.shape(),
1290            Self::View(view) => view.shape(),
1291        }
1292    }
1293
1294    /// Return the referenced rank.
1295    ///
1296    /// # Examples
1297    ///
1298    /// ```rust
1299    /// use tenferro_tensor_core::{Tensor, TensorRef};
1300    ///
1301    /// let tensor = Tensor::from_vec_col_major(vec![1, 1], vec![1_i64])?;
1302    /// assert_eq!(TensorRef::Tensor(&tensor).rank(), 2);
1303    /// # Ok::<(), tenferro_tensor_core::Error>(())
1304    /// ```
1305    pub fn rank(&self) -> usize {
1306        self.shape().len()
1307    }
1308
1309    /// Return whether the referenced tensor/view is empty.
1310    ///
1311    /// # Examples
1312    ///
1313    /// ```rust
1314    /// use tenferro_tensor_core::{Tensor, TensorRef};
1315    ///
1316    /// let tensor = Tensor::from_vec_col_major(vec![0], Vec::<f64>::new())?;
1317    /// assert!(TensorRef::Tensor(&tensor).is_empty());
1318    /// # Ok::<(), tenferro_tensor_core::Error>(())
1319    /// ```
1320    pub fn is_empty(&self) -> bool {
1321        match self {
1322            Self::Tensor(tensor) => tensor.is_empty(),
1323            Self::View(view) => view.is_empty(),
1324        }
1325    }
1326}