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