hurray_core/layout/strided.rs
1//! Strided layout descriptor.
2//!
3//! Tag `0x03`. Carries an explicit per-dimension stride vector.
4//! See `docs/spec/layouts/strided.md`.
5
6/// Descriptor for the strided layout.
7///
8/// The strided layout generalises row-major and column-major by allowing an
9/// arbitrary stride per dimension. Negative strides (reversed dimension) and
10/// zero strides (broadcast dimension) are both valid.
11///
12/// `strides` are in **logical elements**, not bytes.
13///
14/// # Examples
15///
16/// ```
17/// use hurray_core::layout::{LayoutDescriptor, StridedLayout};
18///
19/// // Row-major strides for a 3×4 tensor: [4, 1]
20/// let layout = LayoutDescriptor::Strided(StridedLayout::new(vec![4, 1]));
21/// assert_eq!(layout.tag(), 0x03);
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24#[non_exhaustive]
25pub struct StridedLayout {
26 /// Per-dimension strides in **logical elements**.
27 ///
28 /// Positive: forward; negative: reversed; zero: broadcast (virtual) dimension.
29 /// `strides.len()` MUST equal the tensor rank (validated by
30 /// [`LayoutDescriptor::validate_against_shape`]).
31 pub strides: Vec<i64>,
32}
33
34impl StridedLayout {
35 /// Creates a new [`StridedLayout`] with the given per-dimension strides.
36 ///
37 /// No rank validation is performed here — call
38 /// [`LayoutDescriptor::validate_against_shape`] to check that
39 /// `strides.len() == shape.rank()`.
40 ///
41 /// # Examples
42 ///
43 /// ```
44 /// use hurray_core::layout::StridedLayout;
45 ///
46 /// // Reversed first dimension of a 3×4 tensor (row-major with rows reversed).
47 /// let s = StridedLayout::new(vec![-4, 1]);
48 /// assert_eq!(s.strides, &[-4, 1]);
49 /// ```
50 pub fn new(strides: Vec<i64>) -> Self {
51 Self { strides }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::layout::LayoutDescriptor;
59
60 #[test]
61 fn strided_tag_is_0x03() {
62 let layout = LayoutDescriptor::Strided(StridedLayout::new(vec![4, 1]));
63 assert_eq!(layout.tag(), 0x03);
64 }
65
66 #[test]
67 fn strided_buffer_count_is_1() {
68 use std::num::NonZeroU8;
69 let layout = LayoutDescriptor::Strided(StridedLayout::new(vec![4, 1]));
70 assert_eq!(layout.buffer_count(), Some(NonZeroU8::new(1).unwrap()));
71 }
72
73 #[test]
74 fn strided_allows_negative_and_zero_strides() {
75 // Negative strides (reversed dim) and zero strides (broadcast) are valid.
76 let s = StridedLayout::new(vec![-4, 0, 1]);
77 assert_eq!(s.strides, [-4, 0, 1]);
78 }
79}