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_shape_len(shape: &[usize], data_len: usize) -> Result<usize> {
379 validate_shape_metadata(shape)?;
380 let expected = checked_product(shape)?;
381 if expected != data_len {
382 return Err(Error::ShapeDataLengthMismatch {
383 expected,
384 actual: data_len,
385 });
386 }
387 Ok(expected)
388}
389
390fn validate_shape_metadata(shape: &[usize]) -> Result<()> {
391 checked_product(shape)?;
392 col_major_strides(shape)?;
393 Ok(())
394}
395
396fn compact_col_major_strides(shape: &[usize]) -> StrideVec {
397 // Invariant: HostTensor constructors validate shape metadata before as_view can call this.
398 col_major_strides(shape).expect("HostTensor shape metadata is validated at construction")
399}
400
401/// Return compact column-major strides for a shape.
402///
403/// # Examples
404///
405/// ```rust
406/// use tenferro_tensor_core::col_major_strides;
407///
408/// assert_eq!(col_major_strides(&[2, 3])?.as_slice(), &[1, 2]);
409/// # Ok::<(), tenferro_tensor_core::Error>(())
410/// ```
411pub fn col_major_strides(shape: &[usize]) -> Result<StrideVec> {
412 let mut strides = StrideVec::new();
413 let mut stride = 1isize;
414 for &extent in shape {
415 strides.push(stride);
416 let extent = isize::try_from(extent).map_err(|_| Error::IntegerOverflow)?;
417 stride = stride.checked_mul(extent).ok_or(Error::IntegerOverflow)?;
418 }
419 Ok(strides)
420}
421
422fn validate_permutation(rank: usize, axes: &[usize]) -> Result<()> {
423 if axes.len() != rank {
424 return Err(Error::InvalidPermutationLength {
425 expected: rank,
426 actual: axes.len(),
427 });
428 }
429 let mut seen = vec![false; rank];
430 for &axis in axes {
431 if axis >= rank {
432 return Err(Error::AxisOutOfBounds { axis, rank });
433 }
434 if seen[axis] {
435 return Err(Error::DuplicateAxis { axis });
436 }
437 seen[axis] = true;
438 }
439 Ok(())
440}
441
442fn validate_view_bounds<T>(
443 data: &[T],
444 shape: &[usize],
445 strides: &[isize],
446 offset: isize,
447) -> Result<()> {
448 layout::validate_reachable_bounds(shape, strides, offset, data.len())
449}
450
451fn is_slice_contiguous(shape: &[usize], strides: &[isize]) -> bool {
452 let mut expected = 1isize;
453 for (&extent, &stride) in shape.iter().zip(strides) {
454 if extent <= 1 {
455 continue;
456 }
457 if stride != expected {
458 return false;
459 }
460 let Ok(extent) = isize::try_from(extent) else {
461 return false;
462 };
463 let Some(next) = expected.checked_mul(extent) else {
464 return false;
465 };
466 expected = next;
467 }
468 true
469}
470
471impl<T> HostTensor<T> {
472 /// Create an owned tensor from a column-major host buffer.
473 ///
474 /// # Examples
475 ///
476 /// ```rust
477 /// use tenferro_tensor_core::HostTensor;
478 ///
479 /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1_i64, 2])?;
480 /// assert_eq!(tensor.shape(), &[2]);
481 /// # Ok::<(), tenferro_tensor_core::Error>(())
482 /// ```
483 pub fn from_vec_col_major(shape: impl Into<ShapeVec>, data: Vec<T>) -> Result<Self> {
484 let shape = shape.into();
485 checked_shape_len(&shape, data.len())?;
486 Ok(Self { data, shape })
487 }
488
489 /// Borrow this tensor's shape.
490 ///
491 /// # Examples
492 ///
493 /// ```rust
494 /// use tenferro_tensor_core::HostTensor;
495 ///
496 /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![true, false])?;
497 /// assert_eq!(tensor.shape(), &[2]);
498 /// # Ok::<(), tenferro_tensor_core::Error>(())
499 /// ```
500 pub fn shape(&self) -> &[usize] {
501 &self.shape
502 }
503
504 /// Return the tensor rank.
505 ///
506 /// # Examples
507 ///
508 /// ```rust
509 /// use tenferro_tensor_core::HostTensor;
510 ///
511 /// let tensor = HostTensor::from_vec_col_major(vec![2, 1], vec![1.0_f32, 2.0])?;
512 /// assert_eq!(tensor.rank(), 2);
513 /// # Ok::<(), tenferro_tensor_core::Error>(())
514 /// ```
515 pub fn rank(&self) -> usize {
516 self.shape.len()
517 }
518
519 /// Returns `true` when this tensor has zero elements.
520 ///
521 /// # Examples
522 ///
523 /// ```rust
524 /// use tenferro_tensor_core::HostTensor;
525 ///
526 /// let tensor = HostTensor::<f64>::from_vec_col_major(vec![0], vec![])?;
527 /// assert!(tensor.is_empty());
528 /// # Ok::<(), tenferro_tensor_core::Error>(())
529 /// ```
530 pub fn is_empty(&self) -> bool {
531 self.data.is_empty()
532 }
533
534 /// Borrow the contiguous column-major host buffer.
535 ///
536 /// # Examples
537 ///
538 /// ```rust
539 /// use tenferro_tensor_core::HostTensor;
540 ///
541 /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![7_i32])?;
542 /// assert_eq!(tensor.as_slice(), &[7]);
543 /// # Ok::<(), tenferro_tensor_core::Error>(())
544 /// ```
545 pub fn as_slice(&self) -> &[T] {
546 &self.data
547 }
548
549 /// Mutably borrow the contiguous column-major host buffer.
550 ///
551 /// # Examples
552 ///
553 /// ```rust
554 /// use tenferro_tensor_core::HostTensor;
555 ///
556 /// let mut tensor = HostTensor::from_vec_col_major(vec![1], vec![7_i32])?;
557 /// tensor.as_mut_slice()[0] = 8;
558 /// assert_eq!(tensor.as_slice(), &[8]);
559 /// # Ok::<(), tenferro_tensor_core::Error>(())
560 /// ```
561 pub fn as_mut_slice(&mut self) -> &mut [T] {
562 &mut self.data
563 }
564
565 /// Borrow this tensor as a compact zero-offset view.
566 ///
567 /// # Examples
568 ///
569 /// ```rust
570 /// use tenferro_tensor_core::HostTensor;
571 ///
572 /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
573 /// assert!(tensor.as_view().is_zero_offset_col_major());
574 /// # Ok::<(), tenferro_tensor_core::Error>(())
575 /// ```
576 pub fn as_view(&self) -> HostTensorView<'_, T> {
577 HostTensorView {
578 data: &self.data,
579 shape: self.shape.clone(),
580 strides: compact_col_major_strides(&self.shape),
581 offset: 0,
582 }
583 }
584
585 /// Consume this tensor into its shape and column-major buffer.
586 ///
587 /// # Examples
588 ///
589 /// ```rust
590 /// use tenferro_tensor_core::HostTensor;
591 ///
592 /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![3.0_f64])?;
593 /// assert_eq!(tensor.into_vec_col_major().1, vec![3.0]);
594 /// # Ok::<(), tenferro_tensor_core::Error>(())
595 /// ```
596 pub fn into_vec_col_major(self) -> (ShapeVec, Vec<T>) {
597 (self.shape, self.data)
598 }
599
600 /// Consume this tensor into the same data with a different shape.
601 ///
602 /// # Examples
603 ///
604 /// ```rust
605 /// use tenferro_tensor_core::HostTensor;
606 ///
607 /// let tensor = HostTensor::from_vec_col_major(vec![4], vec![1.0_f64, 2.0, 3.0, 4.0])?;
608 /// assert_eq!(tensor.into_reshaped(vec![2, 2])?.shape(), &[2, 2]);
609 /// # Ok::<(), tenferro_tensor_core::Error>(())
610 /// ```
611 pub fn into_reshaped(self, shape: impl Into<ShapeVec>) -> Result<Self> {
612 let shape = shape.into();
613 let from = self.data.len();
614 let to = checked_product(&shape)?;
615 if from != to {
616 return Err(Error::ReshapeElementCountMismatch { from, to });
617 }
618 validate_shape_metadata(&shape)?;
619 Ok(Self {
620 data: self.data,
621 shape,
622 })
623 }
624}
625
626impl<'a, T> HostTensorView<'a, T> {
627 /// Create a typed view from explicit metadata and validate bounds eagerly.
628 ///
629 /// # Examples
630 ///
631 /// ```rust
632 /// use tenferro_tensor_core::HostTensorView;
633 ///
634 /// let data = [1.0_f64, 2.0, 3.0, 4.0];
635 /// let view = HostTensorView::from_slice(vec![2], vec![1], 1, &data)?;
636 /// assert_eq!(view.as_slice()?, &[2.0, 3.0]);
637 /// # Ok::<(), tenferro_tensor_core::Error>(())
638 /// ```
639 pub fn from_slice(
640 shape: impl Into<ShapeVec>,
641 strides: impl Into<StrideVec>,
642 offset: isize,
643 data: &'a [T],
644 ) -> Result<Self> {
645 let shape = shape.into();
646 let strides = strides.into();
647 validate_view_bounds(data, &shape, &strides, offset)?;
648 Ok(Self {
649 data,
650 shape,
651 strides,
652 offset,
653 })
654 }
655
656 /// Borrow this view's shape.
657 ///
658 /// # Examples
659 ///
660 /// ```rust
661 /// use tenferro_tensor_core::HostTensor;
662 ///
663 /// let tensor = HostTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
664 /// assert_eq!(tensor.as_view().shape(), &[2]);
665 /// # Ok::<(), tenferro_tensor_core::Error>(())
666 /// ```
667 pub fn shape(&self) -> &[usize] {
668 &self.shape
669 }
670
671 /// Borrow this view's signed element strides.
672 ///
673 /// # Examples
674 ///
675 /// ```rust
676 /// use tenferro_tensor_core::HostTensor;
677 ///
678 /// let tensor = HostTensor::from_vec_col_major(vec![2, 3], vec![0_i32; 6])?;
679 /// assert_eq!(tensor.as_view().strides(), &[1, 2]);
680 /// # Ok::<(), tenferro_tensor_core::Error>(())
681 /// ```
682 pub fn strides(&self) -> &[isize] {
683 &self.strides
684 }
685
686 /// Return this view's signed element offset into the backing slice.
687 ///
688 /// # Examples
689 ///
690 /// ```rust
691 /// use tenferro_tensor_core::HostTensor;
692 ///
693 /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![true])?;
694 /// assert_eq!(tensor.as_view().offset(), 0);
695 /// # Ok::<(), tenferro_tensor_core::Error>(())
696 /// ```
697 pub fn offset(&self) -> isize {
698 self.offset
699 }
700
701 /// Return the view rank.
702 ///
703 /// # Examples
704 ///
705 /// ```rust
706 /// use tenferro_tensor_core::HostTensor;
707 ///
708 /// let tensor = HostTensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
709 /// assert_eq!(tensor.as_view().rank(), 2);
710 /// # Ok::<(), tenferro_tensor_core::Error>(())
711 /// ```
712 pub fn rank(&self) -> usize {
713 self.shape.len()
714 }
715
716 /// Returns `true` when this view has zero logical elements.
717 ///
718 /// # Examples
719 ///
720 /// ```rust
721 /// use tenferro_tensor_core::HostTensorView;
722 ///
723 /// let data = [1.0_f64];
724 /// let view = HostTensorView::from_slice(vec![0], vec![1], 0, &data)?;
725 /// assert!(view.is_empty());
726 /// # Ok::<(), tenferro_tensor_core::Error>(())
727 /// ```
728 pub fn is_empty(&self) -> bool {
729 self.shape.contains(&0)
730 }
731
732 /// Return whether this view has compact column-major logical strides.
733 ///
734 /// # Examples
735 ///
736 /// ```rust
737 /// use tenferro_tensor_core::HostTensor;
738 ///
739 /// let tensor = HostTensor::from_vec_col_major(vec![2, 2], vec![0_i32; 4])?;
740 /// assert!(tensor.as_view().is_compact_col_major());
741 /// # Ok::<(), tenferro_tensor_core::Error>(())
742 /// ```
743 pub fn is_compact_col_major(&self) -> bool {
744 is_slice_contiguous(&self.shape, &self.strides)
745 }
746
747 /// Return whether this view is compact column-major and starts at offset zero.
748 ///
749 /// # Examples
750 ///
751 /// ```rust
752 /// use tenferro_tensor_core::HostTensor;
753 ///
754 /// let tensor = HostTensor::from_vec_col_major(vec![1], vec![1_i64])?;
755 /// assert!(tensor.as_view().is_zero_offset_col_major());
756 /// # Ok::<(), tenferro_tensor_core::Error>(())
757 /// ```
758 pub fn is_zero_offset_col_major(&self) -> bool {
759 self.offset == 0 && self.is_compact_col_major()
760 }
761
762 /// Borrow the slice-contiguous backing region for this view.
763 ///
764 /// # Examples
765 ///
766 /// ```rust
767 /// use tenferro_tensor_core::HostTensorView;
768 ///
769 /// let data = [1_i32, 2, 3, 4];
770 /// let view = HostTensorView::from_slice(vec![2], vec![1], 1, &data)?;
771 /// assert_eq!(view.as_slice()?, &[2, 3]);
772 /// # Ok::<(), tenferro_tensor_core::Error>(())
773 /// ```
774 pub fn as_slice(&self) -> Result<&'a [T]> {
775 if !is_slice_contiguous(&self.shape, &self.strides) {
776 return Err(Error::NonContiguousViewAsSlice);
777 }
778 let len = checked_product(&self.shape)?;
779 let start = usize::try_from(self.offset).map_err(|_| Error::IntegerOverflow)?;
780 let end = start.checked_add(len).ok_or(Error::IntegerOverflow)?;
781 self.data.get(start..end).ok_or(Error::ViewOutOfBounds)
782 }
783
784 /// Return a metadata-only reshape of this compact column-major view.
785 ///
786 /// # Examples
787 ///
788 /// ```rust
789 /// use tenferro_tensor_core::HostTensor;
790 ///
791 /// let tensor = HostTensor::from_vec_col_major(vec![4], vec![1.0_f64, 2.0, 3.0, 4.0])?;
792 /// assert_eq!(tensor.as_view().reshape_view(vec![2, 2])?.shape(), &[2, 2]);
793 /// # Ok::<(), tenferro_tensor_core::Error>(())
794 /// ```
795 pub fn reshape_view(&self, shape: impl Into<ShapeVec>) -> Result<Self> {
796 if !self.is_compact_col_major() {
797 return Err(Error::NonContiguousViewAsSlice);
798 }
799 let shape = shape.into();
800 let from = checked_product(&self.shape)?;
801 let to = checked_product(&shape)?;
802 if from != to {
803 return Err(Error::ReshapeElementCountMismatch { from, to });
804 }
805 Self::from_slice(
806 shape.clone(),
807 col_major_strides(&shape)?,
808 self.offset,
809 self.data,
810 )
811 }
812
813 /// Return a metadata-only transposed view with axes in the requested order.
814 ///
815 /// # Examples
816 ///
817 /// ```rust
818 /// use tenferro_tensor_core::HostTensor;
819 ///
820 /// let tensor = HostTensor::from_vec_col_major(vec![2, 3], vec![0_i32; 6])?;
821 /// let view = tensor.as_view().transpose_view(&[1, 0])?;
822 /// assert_eq!(view.shape(), &[3, 2]);
823 /// assert_eq!(view.strides(), &[2, 1]);
824 /// # Ok::<(), tenferro_tensor_core::Error>(())
825 /// ```
826 pub fn transpose_view(&self, axes: &[usize]) -> Result<Self> {
827 validate_permutation(self.rank(), axes)?;
828 let shape = axes
829 .iter()
830 .map(|&axis| self.shape[axis])
831 .collect::<ShapeVec>();
832 let strides = axes
833 .iter()
834 .map(|&axis| self.strides[axis])
835 .collect::<StrideVec>();
836 Self::from_slice(shape, strides, self.offset, self.data)
837 }
838
839 /// Return a metadata-only positive-step slice of this view.
840 ///
841 /// # Examples
842 ///
843 /// ```rust
844 /// use tenferro_tensor_core::{SliceSpec, HostTensor};
845 ///
846 /// let tensor = HostTensor::from_vec_col_major(vec![4], vec![1_i64, 2, 3, 4])?;
847 /// let view = tensor
848 /// .as_view()
849 /// .slice_view(&[SliceSpec { start: 1, end: 4, step: 2 }])?;
850 /// assert_eq!(view.shape(), &[2]);
851 /// # Ok::<(), tenferro_tensor_core::Error>(())
852 /// ```
853 pub fn slice_view(&self, spec: &[SliceSpec]) -> Result<Self> {
854 if spec.len() != self.rank() {
855 return Err(Error::RankMismatch {
856 expected: self.rank(),
857 actual: spec.len(),
858 });
859 }
860 let mut shape = ShapeVec::new();
861 let mut strides = StrideVec::new();
862 let mut offset = self.offset;
863 for ((&axis_len, &stride), slice) in self.shape.iter().zip(self.strides.iter()).zip(spec) {
864 if slice.step <= 0 {
865 return Err(Error::InvalidSliceStep { step: slice.step });
866 }
867 if slice.start < 0 || slice.end < 0 {
868 return Err(Error::InvalidSliceBounds {
869 start: slice.start,
870 end: slice.end,
871 axis_len,
872 });
873 }
874 let start = usize::try_from(slice.start).map_err(|_| Error::IntegerOverflow)?;
875 let end = usize::try_from(slice.end).map_err(|_| Error::IntegerOverflow)?;
876 if start > axis_len || end > axis_len {
877 return Err(Error::InvalidSliceBounds {
878 start: slice.start,
879 end: slice.end,
880 axis_len,
881 });
882 }
883 let step = usize::try_from(slice.step).map_err(|_| Error::IntegerOverflow)?;
884 let extent = if start >= end {
885 0
886 } else {
887 end.checked_sub(start)
888 .and_then(|span| span.checked_add(step - 1))
889 .ok_or(Error::IntegerOverflow)?
890 / step
891 };
892 let start_offset = isize::try_from(start)
893 .map_err(|_| Error::IntegerOverflow)?
894 .checked_mul(stride)
895 .ok_or(Error::IntegerOverflow)?;
896 offset = offset
897 .checked_add(start_offset)
898 .ok_or(Error::IntegerOverflow)?;
899 let new_stride = stride
900 .checked_mul(slice.step)
901 .ok_or(Error::IntegerOverflow)?;
902 shape.push(extent);
903 strides.push(new_stride);
904 }
905 Self::from_slice(shape, strides, offset, self.data)
906 }
907}
908
909impl Tensor {
910 /// Create a dynamic tensor from a column-major host buffer.
911 ///
912 /// # Examples
913 ///
914 /// ```rust
915 /// use tenferro_tensor_core::{DType, Tensor};
916 ///
917 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f32])?;
918 /// assert_eq!(tensor.dtype(), DType::F32);
919 /// # Ok::<(), tenferro_tensor_core::Error>(())
920 /// ```
921 pub fn from_vec_col_major<T: TensorScalar>(
922 shape: impl Into<ShapeVec>,
923 data: Vec<T>,
924 ) -> Result<Self> {
925 let shape = shape.into();
926 checked_shape_len(&shape, data.len())?;
927 Ok(T::into_tensor(shape, data))
928 }
929
930 /// Return the tensor dtype tag.
931 ///
932 /// # Examples
933 ///
934 /// ```rust
935 /// use tenferro_tensor_core::{DType, Tensor};
936 ///
937 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![false])?;
938 /// assert_eq!(tensor.dtype(), DType::Bool);
939 /// # Ok::<(), tenferro_tensor_core::Error>(())
940 /// ```
941 pub fn dtype(&self) -> DType {
942 match self {
943 Self::F32(_) => DType::F32,
944 Self::F64(_) => DType::F64,
945 Self::I32(_) => DType::I32,
946 Self::I64(_) => DType::I64,
947 Self::Bool(_) => DType::Bool,
948 Self::C32(_) => DType::C32,
949 Self::C64(_) => DType::C64,
950 }
951 }
952
953 /// Borrow the tensor shape.
954 ///
955 /// # Examples
956 ///
957 /// ```rust
958 /// use tenferro_tensor_core::Tensor;
959 ///
960 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
961 /// assert_eq!(tensor.shape(), &[2]);
962 /// # Ok::<(), tenferro_tensor_core::Error>(())
963 /// ```
964 pub fn shape(&self) -> &[usize] {
965 match self {
966 Self::F32(t) => t.shape(),
967 Self::F64(t) => t.shape(),
968 Self::I32(t) => t.shape(),
969 Self::I64(t) => t.shape(),
970 Self::Bool(t) => t.shape(),
971 Self::C32(t) => t.shape(),
972 Self::C64(t) => t.shape(),
973 }
974 }
975
976 /// Return the tensor rank.
977 ///
978 /// # Examples
979 ///
980 /// ```rust
981 /// use tenferro_tensor_core::Tensor;
982 ///
983 /// let tensor = Tensor::from_vec_col_major(vec![1, 1], vec![1_i64])?;
984 /// assert_eq!(tensor.rank(), 2);
985 /// # Ok::<(), tenferro_tensor_core::Error>(())
986 /// ```
987 pub fn rank(&self) -> usize {
988 self.shape().len()
989 }
990
991 /// Return whether the tensor has zero elements.
992 ///
993 /// # Examples
994 ///
995 /// ```rust
996 /// use tenferro_tensor_core::Tensor;
997 ///
998 /// let tensor = Tensor::from_vec_col_major(vec![0], Vec::<f64>::new())?;
999 /// assert!(tensor.is_empty());
1000 /// # Ok::<(), tenferro_tensor_core::Error>(())
1001 /// ```
1002 pub fn is_empty(&self) -> bool {
1003 match self {
1004 Self::F32(t) => t.is_empty(),
1005 Self::F64(t) => t.is_empty(),
1006 Self::I32(t) => t.is_empty(),
1007 Self::I64(t) => t.is_empty(),
1008 Self::Bool(t) => t.is_empty(),
1009 Self::C32(t) => t.is_empty(),
1010 Self::C64(t) => t.is_empty(),
1011 }
1012 }
1013
1014 /// Borrow the typed host slice when the dtype matches.
1015 ///
1016 /// # Examples
1017 ///
1018 /// ```rust
1019 /// use tenferro_tensor_core::Tensor;
1020 ///
1021 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![3.0_f64])?;
1022 /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
1023 /// assert!(tensor.as_slice::<f32>().is_err());
1024 /// # Ok::<(), tenferro_tensor_core::Error>(())
1025 /// ```
1026 pub fn as_slice<T: TensorScalar>(&self) -> Result<&[T]> {
1027 T::tensor_slice(self).ok_or(Error::DTypeMismatch {
1028 expected: T::dtype(),
1029 actual: self.dtype(),
1030 })
1031 }
1032
1033 /// Mutably borrow the typed host slice when the dtype matches.
1034 ///
1035 /// # Examples
1036 ///
1037 /// ```rust
1038 /// use tenferro_tensor_core::Tensor;
1039 ///
1040 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![3.0_f64])?;
1041 /// tensor.as_mut_slice::<f64>()?[0] = 4.0;
1042 /// assert_eq!(tensor.as_slice::<f64>()?, &[4.0]);
1043 /// # Ok::<(), tenferro_tensor_core::Error>(())
1044 /// ```
1045 pub fn as_mut_slice<T: TensorScalar>(&mut self) -> Result<&mut [T]> {
1046 let actual = self.dtype();
1047 T::tensor_mut_slice(self).ok_or(Error::DTypeMismatch {
1048 expected: T::dtype(),
1049 actual,
1050 })
1051 }
1052
1053 /// Borrow this tensor as a dynamic zero-offset view.
1054 ///
1055 /// # Examples
1056 ///
1057 /// ```rust
1058 /// use tenferro_tensor_core::{DType, Tensor};
1059 ///
1060 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
1061 /// assert_eq!(tensor.as_view().dtype(), DType::I64);
1062 /// # Ok::<(), tenferro_tensor_core::Error>(())
1063 /// ```
1064 pub fn as_view(&self) -> TensorView<'_> {
1065 match self {
1066 Self::F32(t) => TensorView::F32(t.as_view()),
1067 Self::F64(t) => TensorView::F64(t.as_view()),
1068 Self::I32(t) => TensorView::I32(t.as_view()),
1069 Self::I64(t) => TensorView::I64(t.as_view()),
1070 Self::Bool(t) => TensorView::Bool(t.as_view()),
1071 Self::C32(t) => TensorView::C32(t.as_view()),
1072 Self::C64(t) => TensorView::C64(t.as_view()),
1073 }
1074 }
1075
1076 /// Consume this tensor and return typed column-major data when the dtype matches.
1077 ///
1078 /// # Examples
1079 ///
1080 /// ```rust
1081 /// use tenferro_tensor_core::Tensor;
1082 ///
1083 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f32])?;
1084 /// assert_eq!(tensor.into_vec_col_major::<f32>()?.1, vec![2.0]);
1085 /// # Ok::<(), tenferro_tensor_core::Error>(())
1086 /// ```
1087 pub fn into_vec_col_major<T: TensorScalar>(self) -> Result<(ShapeVec, Vec<T>)> {
1088 let actual = self.dtype();
1089 T::into_typed(self)
1090 .map(HostTensor::into_vec_col_major)
1091 .ok_or(Error::DTypeMismatch {
1092 expected: T::dtype(),
1093 actual,
1094 })
1095 }
1096}
1097
1098macro_rules! impl_dynamic_view {
1099 ($self:ident, $method:ident($($arg:ident),*) => $inner:ident) => {
1100 match $self {
1101 TensorView::F32(view) => TensorView::F32(view.$method($($arg),*)?),
1102 TensorView::F64(view) => TensorView::F64(view.$method($($arg),*)?),
1103 TensorView::I32(view) => TensorView::I32(view.$method($($arg),*)?),
1104 TensorView::I64(view) => TensorView::I64(view.$method($($arg),*)?),
1105 TensorView::Bool(view) => TensorView::Bool(view.$method($($arg),*)?),
1106 TensorView::C32(view) => TensorView::C32(view.$method($($arg),*)?),
1107 TensorView::C64(view) => TensorView::C64(view.$method($($arg),*)?),
1108 }
1109 };
1110}
1111
1112impl<'a> TensorView<'a> {
1113 /// Return this view's dtype.
1114 ///
1115 /// # Examples
1116 ///
1117 /// ```rust
1118 /// use tenferro_tensor_core::{DType, Tensor};
1119 ///
1120 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f32])?;
1121 /// assert_eq!(tensor.as_view().dtype(), DType::F32);
1122 /// # Ok::<(), tenferro_tensor_core::Error>(())
1123 /// ```
1124 pub fn dtype(&self) -> DType {
1125 match self {
1126 Self::F32(_) => DType::F32,
1127 Self::F64(_) => DType::F64,
1128 Self::I32(_) => DType::I32,
1129 Self::I64(_) => DType::I64,
1130 Self::Bool(_) => DType::Bool,
1131 Self::C32(_) => DType::C32,
1132 Self::C64(_) => DType::C64,
1133 }
1134 }
1135
1136 /// Borrow this view's shape.
1137 ///
1138 /// # Examples
1139 ///
1140 /// ```rust
1141 /// use tenferro_tensor_core::Tensor;
1142 ///
1143 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1144 /// assert_eq!(tensor.as_view().shape(), &[1]);
1145 /// # Ok::<(), tenferro_tensor_core::Error>(())
1146 /// ```
1147 pub fn shape(&self) -> &[usize] {
1148 match self {
1149 Self::F32(view) => view.shape(),
1150 Self::F64(view) => view.shape(),
1151 Self::I32(view) => view.shape(),
1152 Self::I64(view) => view.shape(),
1153 Self::Bool(view) => view.shape(),
1154 Self::C32(view) => view.shape(),
1155 Self::C64(view) => view.shape(),
1156 }
1157 }
1158
1159 /// Return the view rank.
1160 ///
1161 /// # Examples
1162 ///
1163 /// ```rust
1164 /// use tenferro_tensor_core::Tensor;
1165 ///
1166 /// let tensor = Tensor::from_vec_col_major(vec![1, 1], vec![1_i64])?;
1167 /// assert_eq!(tensor.as_view().rank(), 2);
1168 /// # Ok::<(), tenferro_tensor_core::Error>(())
1169 /// ```
1170 pub fn rank(&self) -> usize {
1171 self.shape().len()
1172 }
1173
1174 /// Return whether this view has zero logical elements.
1175 ///
1176 /// # Examples
1177 ///
1178 /// ```rust
1179 /// use tenferro_tensor_core::Tensor;
1180 ///
1181 /// let tensor = Tensor::from_vec_col_major(vec![0], Vec::<f64>::new())?;
1182 /// assert!(tensor.as_view().is_empty());
1183 /// # Ok::<(), tenferro_tensor_core::Error>(())
1184 /// ```
1185 pub fn is_empty(&self) -> bool {
1186 match self {
1187 Self::F32(view) => view.is_empty(),
1188 Self::F64(view) => view.is_empty(),
1189 Self::I32(view) => view.is_empty(),
1190 Self::I64(view) => view.is_empty(),
1191 Self::Bool(view) => view.is_empty(),
1192 Self::C32(view) => view.is_empty(),
1193 Self::C64(view) => view.is_empty(),
1194 }
1195 }
1196
1197 /// Return a metadata-only reshape of this dynamic view.
1198 ///
1199 /// # Examples
1200 ///
1201 /// ```rust
1202 /// use tenferro_tensor_core::Tensor;
1203 ///
1204 /// let tensor = Tensor::from_vec_col_major(vec![4], vec![1_i32, 2, 3, 4])?;
1205 /// assert_eq!(tensor.as_view().reshape_view(vec![2, 2])?.shape(), &[2, 2]);
1206 /// # Ok::<(), tenferro_tensor_core::Error>(())
1207 /// ```
1208 pub fn reshape_view(&self, shape: impl Into<ShapeVec>) -> Result<Self> {
1209 let shape = shape.into();
1210 Ok(impl_dynamic_view!(self, reshape_view(shape) => view))
1211 }
1212
1213 /// Return a metadata-only transposed dynamic view with axes in the requested order.
1214 ///
1215 /// # Examples
1216 ///
1217 /// ```rust
1218 /// use tenferro_tensor_core::Tensor;
1219 ///
1220 /// let tensor = Tensor::from_vec_col_major(vec![1, 2], vec![1_i64, 2])?;
1221 /// assert_eq!(tensor.as_view().transpose_view(&[1, 0])?.shape(), &[2, 1]);
1222 /// # Ok::<(), tenferro_tensor_core::Error>(())
1223 /// ```
1224 pub fn transpose_view(&self, axes: &[usize]) -> Result<Self> {
1225 Ok(impl_dynamic_view!(self, transpose_view(axes) => view))
1226 }
1227
1228 /// Return a metadata-only positive-step slice of this dynamic view.
1229 ///
1230 /// # Examples
1231 ///
1232 /// ```rust
1233 /// use tenferro_tensor_core::{SliceSpec, Tensor};
1234 ///
1235 /// let tensor = Tensor::from_vec_col_major(vec![3], vec![1_i64, 2, 3])?;
1236 /// assert_eq!(
1237 /// tensor.as_view().slice_view(&[SliceSpec { start: 1, end: 3, step: 1 }])?.shape(),
1238 /// &[2],
1239 /// );
1240 /// # Ok::<(), tenferro_tensor_core::Error>(())
1241 /// ```
1242 pub fn slice_view(&self, spec: &[SliceSpec]) -> Result<Self> {
1243 Ok(impl_dynamic_view!(self, slice_view(spec) => view))
1244 }
1245}
1246
1247impl<'a> TensorRef<'a> {
1248 /// Return the referenced dtype.
1249 ///
1250 /// # Examples
1251 ///
1252 /// ```rust
1253 /// use tenferro_tensor_core::{DType, Tensor, TensorRef};
1254 ///
1255 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
1256 /// assert_eq!(TensorRef::Tensor(&tensor).dtype(), DType::I64);
1257 /// # Ok::<(), tenferro_tensor_core::Error>(())
1258 /// ```
1259 pub fn dtype(&self) -> DType {
1260 match self {
1261 Self::Tensor(tensor) => tensor.dtype(),
1262 Self::View(view) => view.dtype(),
1263 }
1264 }
1265
1266 /// Borrow the referenced shape.
1267 ///
1268 /// # Examples
1269 ///
1270 /// ```rust
1271 /// use tenferro_tensor_core::{Tensor, TensorRef};
1272 ///
1273 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
1274 /// assert_eq!(TensorRef::Tensor(&tensor).shape(), &[1]);
1275 /// # Ok::<(), tenferro_tensor_core::Error>(())
1276 /// ```
1277 pub fn shape(&self) -> &[usize] {
1278 match self {
1279 Self::Tensor(tensor) => tensor.shape(),
1280 Self::View(view) => view.shape(),
1281 }
1282 }
1283
1284 /// Return the referenced rank.
1285 ///
1286 /// # Examples
1287 ///
1288 /// ```rust
1289 /// use tenferro_tensor_core::{Tensor, TensorRef};
1290 ///
1291 /// let tensor = Tensor::from_vec_col_major(vec![1, 1], vec![1_i64])?;
1292 /// assert_eq!(TensorRef::Tensor(&tensor).rank(), 2);
1293 /// # Ok::<(), tenferro_tensor_core::Error>(())
1294 /// ```
1295 pub fn rank(&self) -> usize {
1296 self.shape().len()
1297 }
1298
1299 /// Return whether the referenced tensor/view is empty.
1300 ///
1301 /// # Examples
1302 ///
1303 /// ```rust
1304 /// use tenferro_tensor_core::{Tensor, TensorRef};
1305 ///
1306 /// let tensor = Tensor::from_vec_col_major(vec![0], Vec::<f64>::new())?;
1307 /// assert!(TensorRef::Tensor(&tensor).is_empty());
1308 /// # Ok::<(), tenferro_tensor_core::Error>(())
1309 /// ```
1310 pub fn is_empty(&self) -> bool {
1311 match self {
1312 Self::Tensor(tensor) => tensor.is_empty(),
1313 Self::View(view) => view.is_empty(),
1314 }
1315 }
1316}