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}