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