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}