Skip to main content

tenferro_tensor_core/
lib.rs

1//! Lightweight host tensor data model and metadata-only views.
2//!
3//! `tenferro-tensor-core` owns backend-independent tensor metadata and
4//! host-resident contiguous tensor storage. It does not own execution backends,
5//! backend buffers, GPU handles, provider selection, or materializing kernels.
6//! Runtime/backend-capable `TypedTensor<T, R>` lives in `tenferro-tensor`.
7//! This crate exposes rank/layout metadata plus host-only tensor adapters.
8//!
9//! # Examples
10//!
11//! ```rust
12//! use tenferro_tensor_core::{HostTensor, Rank, SliceSpec, TensorLayout};
13//!
14//! let tensor = HostTensor::from_vec_col_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0])?;
15//! let view = tensor
16//!     .as_view()
17//!     .slice_view(&[
18//!         SliceSpec { start: 0, end: 2, step: 1 },
19//!         SliceSpec { start: 1, end: 3, step: 1 },
20//!     ])?;
21//!
22//! assert_eq!(view.shape(), &[2, 2]);
23//! assert_eq!(view.as_slice()?, &[3.0, 4.0, 5.0, 6.0]);
24//!
25//! let layout = TensorLayout::<Rank<2>>::compact([2, 3])?;
26//! let transposed = layout.transpose_view([1, 0])?;
27//! assert_eq!(transposed.shape(), &[3, 2]);
28//! # Ok::<(), tenferro_tensor_core::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}