Skip to main content

hermes_simd_core/tensor/view/
mod.rs

1//! Zero-copy N-dimensional strided tensor view.
2//!
3//! [`TensorView`] is the core rank-`N` view over a borrowed slice.  Shape and strides are
4//! `[usize; N]` arrays resolved at compile time — the const generic `N` is erased after
5//! monomorphization, leaving no runtime overhead vs. a hand-written 2-D or 3-D struct.
6//!
7//! This module is split by concern:
8//! - `mod.rs` (here): the core struct, marker impls, constructors, and the
9//!   rank-agnostic shape/stride/element accessors.
10//! - `rank_ops` (private): rank-2 and rank-3 specialized views (rows, columns,
11//!   diagonal, `matrix_at`, transpose).
12//! - `simd_bridge` (private): the zero-copy rank-1 → `SimdView` seam.
13
14use core::marker::PhantomData;
15
16use super::error::TensorError;
17use super::helpers::{compute_offset, row_major_strides};
18use super::layout::{ColMajor, Layout, RowMajor};
19
20mod rank_ops;
21mod simd_bridge;
22
23// ---------------------------------------------------------------------------
24// Core struct
25// ---------------------------------------------------------------------------
26
27/// Zero-copy N-dimensional strided view over a borrowed slice.
28///
29/// # Type Parameters
30/// - `'a` — lifetime of the underlying data slice.
31/// - `T` — element type.
32/// - `N` — tensor rank (number of dimensions). Const generic; resolved at compile time.
33/// - `Layout` — layout marker ZST ([`RowMajor`] or [`ColMajor`]). `PhantomData`; zero size.
34/// - `Ref` — reference type-state (`&'a [T]` or `&'a mut [T]`).
35///
36/// # Invariants
37/// - `strides[i] * shape[i]` must not overflow `usize`.
38/// - `data.len() >= ∑ (shape[i]-1) * strides[i] + 1` for any valid element access.
39/// - `new` and `with_strides` both verify `product(shape) <= data.len()`.
40pub struct TensorView<'a, T: 'a, const N: usize, Layout = RowMajor, Ref = &'a [T]> {
41    pub(super) ptr: *mut [T],
42    pub(super) shape: [usize; N],
43    pub(super) strides: [usize; N],
44    pub(super) _layout: PhantomData<(&'a T, Layout, Ref)>,
45}
46
47// ---------------------------------------------------------------------------
48// Send / Sync / Clone / Copy
49// ---------------------------------------------------------------------------
50
51unsafe impl<'a, T, const N: usize, Layout, Ref> Send for TensorView<'a, T, N, Layout, Ref> where
52    Ref: Send
53{
54}
55
56unsafe impl<'a, T, const N: usize, Layout, Ref> Sync for TensorView<'a, T, N, Layout, Ref> where
57    Ref: Sync
58{
59}
60
61impl<'a, T, const N: usize, Layout> Clone for TensorView<'a, T, N, Layout, &'a [T]> {
62    #[inline(always)]
63    fn clone(&self) -> Self {
64        *self
65    }
66}
67
68impl<'a, T, const N: usize, Layout> Copy for TensorView<'a, T, N, Layout, &'a [T]> {}
69
70// ---------------------------------------------------------------------------
71// Row-major immutable constructor
72// ---------------------------------------------------------------------------
73
74impl<'a, 'b, T, const N: usize> TensorView<'a, T, N, RowMajor, &'b [T]> {
75    /// Create a row-major tensor view over `data` with the given `shape`.
76    ///
77    /// Strides are computed as `strides[i] = ∏_{j=i+1..N} shape[j]` (C-order).
78    ///
79    /// # Errors
80    /// Returns [`TensorError::ShapeMismatch`] if `∏ shape > data.len()`.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use hermes_simd_core::TensorView;
86    ///
87    /// let data = [0, 1, 2, 3, 4, 5];
88    /// let view = TensorView::<i32, 2>::new(&data, [2, 3]).unwrap();
89    ///
90    /// assert_eq!(view.shape(), [2, 3]);
91    /// assert_eq!(view.strides(), [3, 1]);
92    /// assert_eq!(view.get([1, 2]).unwrap(), 5);
93    /// ```
94    #[inline]
95    pub fn new(data: &'b [T], shape: [usize; N]) -> Result<Self, TensorError> {
96        let elem_count = shape.iter().product::<usize>();
97        if elem_count > data.len() {
98            return Err(TensorError::ShapeMismatch);
99        }
100        let strides = row_major_strides(shape);
101        Ok(Self {
102            ptr: data as *const [T] as *mut [T],
103            shape,
104            strides,
105            _layout: PhantomData,
106        })
107    }
108}
109
110// ---------------------------------------------------------------------------
111// Row-major mutable constructor
112// ---------------------------------------------------------------------------
113
114impl<'a, 'b, T, const N: usize> TensorView<'a, T, N, RowMajor, &'b mut [T]> {
115    /// Create a mutable row-major tensor view over `data` with the given `shape`.
116    ///
117    /// # Errors
118    /// Returns [`TensorError::ShapeMismatch`] if `∏ shape > data.len()`.
119    #[inline]
120    pub fn new_mut(data: &'b mut [T], shape: [usize; N]) -> Result<Self, TensorError> {
121        let elem_count = shape.iter().product::<usize>();
122        if elem_count > data.len() {
123            return Err(TensorError::ShapeMismatch);
124        }
125        let strides = row_major_strides(shape);
126        Ok(Self {
127            ptr: data as *mut [T],
128            shape,
129            strides,
130            _layout: PhantomData,
131        })
132    }
133}
134
135// ---------------------------------------------------------------------------
136// Explicit-stride constructors (immutable + mutable)
137// ---------------------------------------------------------------------------
138
139impl<'a, 'b, T, const N: usize, L: Layout> TensorView<'a, T, N, L, &'b [T]> {
140    /// Create a tensor view with explicit strides.
141    ///
142    /// Allows column-major, blocked, or any custom layout.
143    ///
144    /// # Errors
145    /// Returns [`TensorError::ShapeMismatch`] if `∏ shape > data.len()`.
146    #[inline]
147    pub fn with_strides(
148        data: &'b [T],
149        shape: [usize; N],
150        strides: [usize; N],
151    ) -> Result<Self, TensorError> {
152        let elem_count = shape.iter().product::<usize>();
153        if elem_count > data.len() {
154            return Err(TensorError::ShapeMismatch);
155        }
156        Ok(Self {
157            ptr: data as *const [T] as *mut [T],
158            shape,
159            strides,
160            _layout: PhantomData,
161        })
162    }
163}
164
165impl<'a, 'b, T, const N: usize, L: Layout> TensorView<'a, T, N, L, &'b mut [T]> {
166    /// Create a mutable tensor view with explicit strides.
167    ///
168    /// # Errors
169    /// Returns [`TensorError::ShapeMismatch`] if `∏ shape > data.len()`.
170    #[inline]
171    pub fn with_strides_mut(
172        data: &'b mut [T],
173        shape: [usize; N],
174        strides: [usize; N],
175    ) -> Result<Self, TensorError> {
176        let elem_count = shape.iter().product::<usize>();
177        if elem_count > data.len() {
178            return Err(TensorError::ShapeMismatch);
179        }
180        Ok(Self {
181            ptr: data as *mut [T],
182            shape,
183            strides,
184            _layout: PhantomData,
185        })
186    }
187
188    /// Downgrade the exclusive mutable view to a shared read-only view.
189    #[inline(always)]
190    pub fn downgrade(self) -> TensorView<'a, T, N, L, &'b [T]> {
191        TensorView {
192            ptr: self.ptr,
193            shape: self.shape,
194            strides: self.strides,
195            _layout: PhantomData,
196        }
197    }
198}
199
200// ---------------------------------------------------------------------------
201// ColMajor ergonomic constructor
202// ---------------------------------------------------------------------------
203
204impl<'a, 'b, T> TensorView<'a, T, 2, ColMajor, &'b [T]> {
205    /// Create a column-major (Fortran-order) 2-D tensor view.
206    ///
207    /// Fortran strides: `strides[0] = 1`, `strides[1] = shape[0]`.
208    ///
209    /// # Errors
210    /// Returns [`TensorError::ShapeMismatch`] if `shape[0] * shape[1] > data.len()`.
211    #[inline]
212    pub fn new_col_major(data: &'b [T], shape: [usize; 2]) -> Result<Self, TensorError> {
213        let elem_count = shape[0] * shape[1];
214        if elem_count > data.len() {
215            return Err(TensorError::ShapeMismatch);
216        }
217        // Fortran strides: strides[0] = 1 (column-stride), strides[1] = nrows (row-stride).
218        let strides = [1, shape[0]];
219        Ok(Self {
220            ptr: data as *const [T] as *mut [T],
221            shape,
222            strides,
223            _layout: PhantomData,
224        })
225    }
226}
227
228// ---------------------------------------------------------------------------
229// Shape / stride / element accessors (all Ref variants)
230// ---------------------------------------------------------------------------
231
232impl<'a, T, const N: usize, L, Ref> TensorView<'a, T, N, L, Ref> {
233    /// The logical shape of this tensor: number of elements per dimension.
234    #[inline(always)]
235    pub fn shape(&self) -> [usize; N] {
236        self.shape
237    }
238
239    /// The strides of this tensor in element units.
240    #[inline(always)]
241    pub fn strides(&self) -> [usize; N] {
242        self.strides
243    }
244
245    /// Number of elements in this tensor: `∏ shape[i]`.
246    #[inline]
247    pub fn num_elements(&self) -> usize {
248        self.shape.iter().product()
249    }
250
251    /// Returns `true` if the tensor is empty (one of its dimensions is 0).
252    #[inline]
253    pub fn is_empty(&self) -> bool {
254        self.num_elements() == 0
255    }
256
257    /// Whether this view is contiguous in row-major order.
258    #[inline]
259    pub fn is_contiguous(&self) -> bool {
260        let expected = row_major_strides(self.shape);
261        self.strides == expected
262    }
263
264    /// View the underlying flat slice (in storage order).
265    #[inline(always)]
266    pub fn as_slice(&self) -> &[T] {
267        unsafe { &*self.ptr }
268    }
269
270    /// Bounds-checked element access.
271    #[inline]
272    pub fn get(&self, idx: [usize; N]) -> Result<T, TensorError>
273    where
274        T: Copy,
275    {
276        for i in 0..N {
277            if idx[i] >= self.shape[i] {
278                return Err(TensorError::IndexOutOfBounds);
279            }
280        }
281        let offset = compute_offset(&idx, &self.strides);
282        Ok(self.as_slice()[offset])
283    }
284
285    /// Unchecked element access.
286    ///
287    /// # Safety
288    /// `idx[i] < shape[i]` for all `i`.
289    #[inline(always)]
290    pub unsafe fn get_unchecked(&self, idx: [usize; N]) -> T
291    where
292        T: Copy,
293    {
294        let offset = compute_offset(&idx, &self.strides);
295        *self.as_slice().get_unchecked(offset)
296    }
297
298    /// Reshape this view to a different rank `M`, reusing the same flat slice.
299    #[inline]
300    pub fn reshape<const M: usize>(
301        self,
302        new_shape: [usize; M],
303    ) -> Result<TensorView<'a, T, M, RowMajor, Ref>, TensorError> {
304        if !self.is_contiguous() {
305            return Err(TensorError::NotContiguous);
306        }
307        let old_count: usize = self.shape.iter().product();
308        let new_count: usize = new_shape.iter().product();
309        if old_count != new_count {
310            return Err(TensorError::ShapeMismatch);
311        }
312        let strides = row_major_strides(new_shape);
313        Ok(TensorView {
314            ptr: self.ptr,
315            shape: new_shape,
316            strides,
317            _layout: PhantomData,
318        })
319    }
320}
321
322// ---------------------------------------------------------------------------
323// Mutable element access
324// ---------------------------------------------------------------------------
325
326impl<'a, 'b, T, const N: usize, L> TensorView<'a, T, N, L, &'b mut [T]> {
327    /// Access the underlying flat mutable slice.
328    #[inline(always)]
329    pub fn as_slice_mut(&mut self) -> &mut [T] {
330        unsafe { &mut *self.ptr }
331    }
332
333    /// Bounds-checked element write access.
334    #[inline]
335    pub fn set(&mut self, idx: [usize; N], val: T) -> Result<(), TensorError> {
336        for i in 0..N {
337            if idx[i] >= self.shape[i] {
338                return Err(TensorError::IndexOutOfBounds);
339            }
340        }
341        let offset = compute_offset(&idx, &self.strides);
342        self.as_slice_mut()[offset] = val;
343        Ok(())
344    }
345
346    /// Unchecked element write access.
347    ///
348    /// # Safety
349    /// `idx[i] < shape[i]` for all `i`.
350    #[inline(always)]
351    pub unsafe fn set_unchecked(&mut self, idx: [usize; N], val: T) {
352        let offset = compute_offset(&idx, &self.strides);
353        *self.as_slice_mut().get_unchecked_mut(offset) = val;
354    }
355}