Skip to main content

ferray_core/
layout.rs

1// ferray-core: Memory layout enum (REQ-1)
2
3/// Describes the memory layout of an N-dimensional array.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum MemoryLayout {
6    /// Row-major (C-style) contiguous layout.
7    C,
8    /// Column-major (Fortran-style) contiguous layout.
9    Fortran,
10    /// Non-contiguous or custom stride layout.
11    Custom,
12}
13
14impl MemoryLayout {
15    /// Returns `true` if the layout is C-contiguous (row-major).
16    #[inline]
17    #[must_use]
18    pub fn is_c_contiguous(self) -> bool {
19        self == Self::C
20    }
21
22    /// Returns `true` if the layout is Fortran-contiguous (column-major).
23    #[inline]
24    #[must_use]
25    pub fn is_f_contiguous(self) -> bool {
26        self == Self::Fortran
27    }
28
29    /// Returns `true` if the layout is neither C nor Fortran contiguous.
30    #[inline]
31    #[must_use]
32    pub fn is_custom(self) -> bool {
33        self == Self::Custom
34    }
35}
36
37impl core::fmt::Display for MemoryLayout {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        match self {
40            Self::C => write!(f, "C"),
41            Self::Fortran => write!(f, "F"),
42            Self::Custom => write!(f, "Custom"),
43        }
44    }
45}
46
47/// Classify the memory layout for an ndarray-backed array given
48/// whether it is already known to be C-standard and its current shape +
49/// element strides. Used by `Array::layout`, `ArrayView::layout` and
50/// `ArrayViewMut::layout` so all three share one implementation
51/// (see issue #127).
52#[cfg(feature = "std")]
53#[inline]
54pub(crate) fn classify_layout(
55    is_standard: bool,
56    shape: &[usize],
57    strides: &[isize],
58) -> MemoryLayout {
59    if is_standard {
60        MemoryLayout::C
61    } else {
62        detect_layout(shape, strides)
63    }
64}
65
66/// Determine memory layout from shape and strides.
67#[cfg(feature = "std")]
68pub(crate) fn detect_layout(shape: &[usize], strides: &[isize]) -> MemoryLayout {
69    if shape.is_empty() {
70        return MemoryLayout::C; // scalar-like
71    }
72
73    let is_c = is_c_contiguous(shape, strides);
74    let is_f = is_f_contiguous(shape, strides);
75
76    if is_c {
77        MemoryLayout::C
78    } else if is_f {
79        MemoryLayout::Fortran
80    } else {
81        MemoryLayout::Custom
82    }
83}
84
85#[cfg(feature = "std")]
86fn is_c_contiguous(shape: &[usize], strides: &[isize]) -> bool {
87    if shape.len() != strides.len() {
88        return false;
89    }
90    let ndim = shape.len();
91    if ndim == 0 {
92        return true;
93    }
94    let mut expected: isize = 1;
95    for i in (0..ndim).rev() {
96        if shape[i] == 0 {
97            return true; // empty array is contiguous by convention
98        }
99        if shape[i] != 1 && strides[i] != expected {
100            return false;
101        }
102        expected = strides[i] * shape[i] as isize;
103    }
104    true
105}
106
107#[cfg(feature = "std")]
108fn is_f_contiguous(shape: &[usize], strides: &[isize]) -> bool {
109    if shape.len() != strides.len() {
110        return false;
111    }
112    let ndim = shape.len();
113    if ndim == 0 {
114        return true;
115    }
116    let mut expected: isize = 1;
117    for i in 0..ndim {
118        if shape[i] == 0 {
119            return true; // empty array is contiguous by convention
120        }
121        if shape[i] != 1 && strides[i] != expected {
122            return false;
123        }
124        expected = strides[i] * shape[i] as isize;
125    }
126    true
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn detect_c_contiguous() {
135        // 3x4 C-contiguous: strides = [4, 1]
136        assert_eq!(detect_layout(&[3, 4], &[4, 1]), MemoryLayout::C);
137    }
138
139    #[test]
140    fn detect_f_contiguous() {
141        // 3x4 F-contiguous: strides = [1, 3]
142        assert_eq!(detect_layout(&[3, 4], &[1, 3]), MemoryLayout::Fortran);
143    }
144
145    #[test]
146    fn detect_custom() {
147        // non-contiguous strides
148        assert_eq!(detect_layout(&[3, 4], &[8, 2]), MemoryLayout::Custom);
149    }
150
151    #[test]
152    fn detect_empty() {
153        assert_eq!(detect_layout(&[], &[]), MemoryLayout::C);
154    }
155
156    #[test]
157    fn display() {
158        assert_eq!(MemoryLayout::C.to_string(), "C");
159        assert_eq!(MemoryLayout::Fortran.to_string(), "F");
160        assert_eq!(MemoryLayout::Custom.to_string(), "Custom");
161    }
162}