Skip to main content

tenferro_tensor_core/
lib.rs

1//! Backend-independent rank/layout, dtype and scalar metadata.
2//!
3//! `tenferro-tensor-core` owns backend-independent tensor metadata: scalar
4//! tags and promotion facts, rank/layout metadata, and layout validation. It
5//! does not own tensor storage, execution backends, backend buffers, GPU
6//! handles, provider selection, or materializing kernels. The tensor families
7//! that carry storage live in `tenferro-tensor`.
8//!
9//! # Examples
10//!
11//! ```rust
12//! use tenferro_tensor_core::{DType, Rank, TensorLayout};
13//!
14//! let layout = TensorLayout::<Rank<2>>::compact([2, 3])?;
15//! let transposed = layout.transpose_view([1, 0])?;
16//! assert_eq!(transposed.shape(), &[3, 2]);
17//!
18//! assert_eq!(DType::F64.spec().width, 64);
19//! # Ok::<(), tenferro_tensor_core::ValidationError>(())
20//! ```
21
22use num_complex::{Complex32, Complex64};
23use tinyvec::TinyVec;
24
25mod error;
26mod layout;
27mod rank;
28mod scalar;
29#[macro_use]
30mod scalar_set;
31
32pub use error::{ErrorKind, ShapeMismatch, ValidationError, ValidationKind};
33pub use layout::TensorLayout;
34pub use rank::{DynRank, IntoRankShape, Rank, TensorRank};
35pub use scalar::{ad_admission, AdAdmissionError, Scalar, ScalarArithmetic, ScalarDomain};
36pub use scalar_set::{promote_in_set, promote_specs, MemberKind, MemberSpec};
37
38/// Maximum rank whose shape/stride metadata stays inline without a heap
39/// allocation.
40///
41/// Mirrors `strided_view::INLINE_RANK`; `tenferro-cpu-basic` carries a
42/// compile-time assertion that the two values agree. At rank `<= INLINE_RANK`
43/// [`ShapeVec`] and [`StrideVec`] keep their elements inline; a higher rank
44/// spills to the heap gracefully.
45///
46/// # Examples
47///
48/// ```rust
49/// use tenferro_tensor_core::INLINE_RANK;
50///
51/// assert_eq!(INLINE_RANK, 8);
52/// ```
53pub const INLINE_RANK: usize = 8;
54
55/// Small tensor shape vector with inline capacity for common dynamic ranks.
56///
57/// # Examples
58///
59/// ```rust
60/// use tenferro_tensor_core::ShapeVec;
61///
62/// let shape: ShapeVec = vec![2, 3].into_iter().collect();
63/// assert_eq!(shape.as_slice(), &[2, 3]);
64/// ```
65pub type ShapeVec = TinyVec<[usize; INLINE_RANK]>;
66
67/// Convert a common shape container into the dynamic owned shape type.
68///
69/// Arrays, vectors, slices, and [`ShapeVec`] implement this trait through
70/// their `AsRef<[usize]>` representation.
71pub trait IntoShapeVec {
72    /// Convert this shape container into an owned [`ShapeVec`].
73    fn into_shape_vec(self) -> ShapeVec;
74}
75
76impl<S> IntoShapeVec for S
77where
78    S: AsRef<[usize]>,
79{
80    fn into_shape_vec(self) -> ShapeVec {
81        self.as_ref().iter().copied().collect()
82    }
83}
84
85/// Small tensor stride vector with signed element strides.
86///
87/// # Examples
88///
89/// ```rust
90/// use tenferro_tensor_core::StrideVec;
91///
92/// let strides: StrideVec = vec![1, 2].into_iter().collect();
93/// assert_eq!(strides.as_slice(), &[1, 2]);
94/// ```
95pub type StrideVec = TinyVec<[isize; INLINE_RANK]>;
96
97/// Result type for tensor data-model operations.
98///
99/// # Examples
100///
101/// ```rust
102/// use tenferro_tensor_core::{Result, ValidationError};
103///
104/// let result: Result<()> = Err(ValidationError::RankMismatch { expected: 2, actual: 1 });
105/// assert!(result.is_err());
106/// ```
107pub type Result<T> = std::result::Result<T, ValidationError>;
108
109define_scalar_tag! {
110    /// Runtime scalar dtype tag.
111    ///
112    /// # Examples
113    ///
114    /// ```rust
115    /// use tenferro_tensor_core::DType;
116    ///
117    /// assert_eq!(DType::F64, DType::F64);
118    /// ```
119    pub enum DType {
120        /// 32-bit floating point.
121        F32 => f32 : Float 0 32,
122        /// 64-bit floating point.
123        F64 => f64 : Float 1 64,
124        /// 32-bit signed integer.
125        I32 => i32 : Integer 0 32,
126        /// 64-bit signed integer.
127        I64 => i64 : Integer 1 64,
128        /// Boolean.
129        Bool => bool : Boolean 0 0,
130        /// 32-bit complex floating point.
131        C32 => Complex32 : Complex 0 32,
132        /// 64-bit complex floating point.
133        C64 => Complex64 : Complex 1 64,
134    }
135    external External(core::any::TypeId);
136}
137
138/// Sealed trait for scalar types supported by the core tensor data model.
139///
140/// # Examples
141///
142/// ```rust
143/// use tenferro_tensor_core::{DType, TensorScalar};
144///
145/// assert_eq!(f64::dtype(), DType::F64);
146/// assert_eq!(num_complex::Complex64::dtype(), DType::C64);
147/// ```
148pub trait TensorScalar: Scalar + Copy + Clone + Send + Sync + 'static + private::Sealed {
149    /// Real-valued counterpart of this scalar type.
150    type Real: TensorScalar;
151
152    /// Return the scalar dtype tag.
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use tenferro_tensor_core::{DType, TensorScalar};
158    ///
159    /// assert_eq!(i64::dtype(), DType::I64);
160    /// ```
161    fn dtype() -> DType;
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! scalar_domain {
177    (float) => {
178        ScalarDomain::Field
179    };
180    (complex) => {
181        ScalarDomain::Field
182    };
183    (integer) => {
184        ScalarDomain::Field
185    };
186    (boolean) => {
187        ScalarDomain::NonField
188    };
189}
190
191macro_rules! impl_scalar_arithmetic {
192    ($ty:ty, float) => {
193        impl ScalarArithmetic for $ty {
194            fn scalar_zero() -> Self {
195                0.0
196            }
197
198            fn scalar_one() -> Self {
199                1.0
200            }
201
202            fn scalar_add(self, rhs: Self) -> Self {
203                std::ops::Add::add(self, rhs)
204            }
205
206            fn scalar_sub(self, rhs: Self) -> Self {
207                std::ops::Sub::sub(self, rhs)
208            }
209
210            fn scalar_mul(self, rhs: Self) -> Self {
211                std::ops::Mul::mul(self, rhs)
212            }
213        }
214    };
215    ($ty:ty, complex) => {
216        impl ScalarArithmetic for $ty {
217            fn scalar_zero() -> Self {
218                <$ty>::new(0.0, 0.0)
219            }
220
221            fn scalar_one() -> Self {
222                <$ty>::new(1.0, 0.0)
223            }
224
225            fn scalar_add(self, rhs: Self) -> Self {
226                std::ops::Add::add(self, rhs)
227            }
228
229            fn scalar_sub(self, rhs: Self) -> Self {
230                std::ops::Sub::sub(self, rhs)
231            }
232
233            fn scalar_mul(self, rhs: Self) -> Self {
234                std::ops::Mul::mul(self, rhs)
235            }
236        }
237    };
238    ($ty:ty, integer) => {
239        impl ScalarArithmetic for $ty {
240            fn scalar_zero() -> Self {
241                0
242            }
243
244            fn scalar_one() -> Self {
245                1
246            }
247
248            fn scalar_add(self, rhs: Self) -> Self {
249                self.wrapping_add(rhs)
250            }
251
252            fn scalar_sub(self, rhs: Self) -> Self {
253                self.wrapping_sub(rhs)
254            }
255
256            fn scalar_mul(self, rhs: Self) -> Self {
257                self.wrapping_mul(rhs)
258            }
259        }
260    };
261    ($ty:ty, boolean) => {};
262}
263
264macro_rules! impl_scalar {
265    ($ty:ty, $real:ty, $dtype:expr, $variant:ident, $kind:ident) => {
266        impl Scalar for $ty {
267            const DOMAIN: ScalarDomain = scalar_domain!($kind);
268        }
269
270        impl_scalar_arithmetic!($ty, $kind);
271
272        impl TensorScalar for $ty {
273            type Real = $real;
274
275            fn dtype() -> DType {
276                $dtype
277            }
278        }
279    };
280}
281
282// The single preset table: tag, real counterpart, erased variant, and algebra
283// kind are declared once here and expanded into every contract the preset
284// scalars implement.
285impl_scalar!(f32, f32, DType::F32, F32, float);
286impl_scalar!(f64, f64, DType::F64, F64, float);
287impl_scalar!(i32, i32, DType::I32, I32, integer);
288impl_scalar!(i64, i64, DType::I64, I64, integer);
289impl_scalar!(bool, bool, DType::Bool, Bool, boolean);
290impl_scalar!(Complex32, f32, DType::C32, C32, complex);
291impl_scalar!(Complex64, f64, DType::C64, C64, complex);
292
293/// Explicit slice descriptor.
294///
295/// A zero step is invalid. Layout metadata APIs support signed steps when
296/// reachable-range validation proves the view stays inside the backing
297/// allocation.
298///
299/// # Examples
300///
301/// ```rust
302/// use tenferro_tensor_core::SliceSpec;
303///
304/// let spec = SliceSpec { start: 1, end: 4, step: 2 };
305/// assert_eq!(spec.step, 2);
306/// ```
307#[derive(Clone, Copy, Debug, PartialEq, Eq)]
308pub struct SliceSpec {
309    pub start: isize,
310    pub end: isize,
311    pub step: isize,
312}
313
314// A valid empty slice. `tinyvec`'s storage (`Array::Item: Default`) needs a
315// default value to initialize unused inline slots; unlike a derived default,
316// `step` stays nonzero so the value is a legal `SliceSpec`.
317impl Default for SliceSpec {
318    fn default() -> Self {
319        Self {
320            start: 0,
321            end: 0,
322            step: 1,
323        }
324    }
325}
326
327fn checked_product(shape: &[usize]) -> Result<usize> {
328    shape.iter().try_fold(1usize, |acc, &dim| {
329        acc.checked_mul(dim).ok_or(ValidationError::IntegerOverflow)
330    })
331}
332
333fn checked_logical_element_count(shape: &[usize]) -> Result<usize> {
334    if shape.contains(&0) {
335        return Ok(0);
336    }
337    checked_product(shape)
338}
339
340/// Return compact column-major strides for a shape.
341///
342/// # Examples
343///
344/// ```rust
345/// use tenferro_tensor_core::col_major_strides;
346///
347/// assert_eq!(col_major_strides(&[2, 3])?.as_slice(), &[1, 2]);
348/// # Ok::<(), tenferro_tensor_core::ValidationError>(())
349/// ```
350///
351/// # Errors
352///
353/// Returns [`ValidationError::IntegerOverflow`] when a stride or extent
354/// cannot be represented by the metadata arithmetic.
355pub fn col_major_strides(shape: &[usize]) -> Result<StrideVec> {
356    let mut strides = StrideVec::new();
357    let mut stride = 1isize;
358    for &extent in shape {
359        strides.push(stride);
360        let extent = isize::try_from(extent).map_err(|_| ValidationError::IntegerOverflow)?;
361        stride = stride
362            .checked_mul(extent)
363            .ok_or(ValidationError::IntegerOverflow)?;
364    }
365    Ok(strides)
366}
367
368fn validate_permutation(rank: usize, axes: &[usize]) -> Result<()> {
369    if axes.len() != rank {
370        return Err(ValidationError::InvalidPermutationLength {
371            expected: rank,
372            actual: axes.len(),
373        });
374    }
375    // Common ranks track seen axes in a bitmask; only very high ranks allocate.
376    if rank <= u128::BITS as usize {
377        let mut seen = 0u128;
378        for &axis in axes {
379            if axis >= rank {
380                return Err(ValidationError::AxisOutOfBounds { axis, rank });
381            }
382            let bit = 1u128 << axis;
383            if seen & bit != 0 {
384                return Err(ValidationError::DuplicateAxis {
385                    axis,
386                    role: "permutation",
387                });
388            }
389            seen |= bit;
390        }
391        return Ok(());
392    }
393    let mut seen = vec![false; rank];
394    for &axis in axes {
395        if axis >= rank {
396            return Err(ValidationError::AxisOutOfBounds { axis, rank });
397        }
398        if seen[axis] {
399            return Err(ValidationError::DuplicateAxis {
400                axis,
401                role: "permutation",
402            });
403        }
404        seen[axis] = true;
405    }
406    Ok(())
407}