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}