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