Skip to main content

ferray_core/array/
introspect.rs

1// ferray-core: Array introspection properties (REQ-35, REQ-36)
2
3use crate::dimension::Dimension;
4use crate::dtype::{DType, Element};
5use crate::error::{FerrayError, FerrayResult};
6
7use super::ArrayFlags;
8use super::owned::Array;
9use super::view::ArrayView;
10
11// ---------------------------------------------------------------------------
12// REQ-35: Core introspection for Array<T, D>
13// ---------------------------------------------------------------------------
14
15impl<T: Element, D: Dimension> Array<T, D> {
16    /// Size in bytes of a single element.
17    #[inline]
18    pub const fn itemsize(&self) -> usize {
19        std::mem::size_of::<T>()
20    }
21
22    /// Total size in bytes of all elements (size * itemsize).
23    #[inline]
24    pub fn nbytes(&self) -> usize {
25        self.size() * self.itemsize()
26    }
27
28    /// Runtime dtype tag for this array's element type.
29    #[inline]
30    pub fn dtype(&self) -> DType {
31        T::dtype()
32    }
33
34    // -- REQ-36 additional properties --
35
36    /// Transposed view (zero-copy). Reverses the axes.
37    ///
38    /// This is the equivalent of `NumPy`'s `.T` property.
39    pub fn t(&self) -> ArrayView<'_, T, D> {
40        let transposed = self.inner.view().reversed_axes();
41        ArrayView::from_ndarray(transposed)
42    }
43
44    /// Deep copy of this array.
45    #[must_use]
46    pub fn copy(&self) -> Self {
47        Self {
48            inner: self.inner.clone(),
49            dim: self.dim.clone(),
50        }
51    }
52
53    /// Convert to a flat `Vec<T>` in logical (row-major) order.
54    pub fn to_vec_flat(&self) -> Vec<T> {
55        self.inner.iter().cloned().collect()
56    }
57
58    /// Return the raw bytes of the array data.
59    ///
60    /// Only succeeds if the array is contiguous; returns an error otherwise.
61    /// Requires `T: Copy` to guarantee no padding bytes exist.
62    pub fn to_bytes(&self) -> FerrayResult<&[u8]>
63    where
64        T: Copy,
65    {
66        let slice = self.inner.as_slice().ok_or_else(|| {
67            FerrayError::invalid_value("array is not contiguous; cannot produce byte slice")
68        })?;
69        let ptr = slice.as_ptr().cast::<u8>();
70        let len = std::mem::size_of_val(slice);
71        // SAFETY: the slice is contiguous and alive for 'self lifetime;
72        // reinterpreting as bytes is always safe for Copy-like types.
73        Ok(unsafe { std::slice::from_raw_parts(ptr, len) })
74    }
75
76    /// Return flags describing memory properties.
77    pub fn flags(&self) -> ArrayFlags {
78        let layout = self.layout();
79        ArrayFlags {
80            c_contiguous: layout.is_c_contiguous(),
81            f_contiguous: layout.is_f_contiguous(),
82            owndata: true,
83            writeable: true,
84            // Owned arrays go through Vec<T>, whose allocator
85            // guarantees alignment for T (#345).
86            aligned: true,
87        }
88    }
89}
90
91// ---------------------------------------------------------------------------
92// REQ-35: Core introspection for ArrayView<'a, T, D>
93// ---------------------------------------------------------------------------
94
95impl<T: Element, D: Dimension> ArrayView<'_, T, D> {
96    /// Size in bytes of a single element.
97    #[inline]
98    pub const fn itemsize(&self) -> usize {
99        std::mem::size_of::<T>()
100    }
101
102    /// Total size in bytes of all elements.
103    #[inline]
104    pub fn nbytes(&self) -> usize {
105        self.size() * self.itemsize()
106    }
107
108    /// Runtime dtype tag.
109    #[inline]
110    pub fn dtype(&self) -> DType {
111        T::dtype()
112    }
113
114    /// Transposed view (zero-copy).
115    pub fn t(&self) -> ArrayView<'_, T, D> {
116        let transposed = self.inner.clone().reversed_axes();
117        ArrayView::from_ndarray(transposed)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::dimension::{Ix1, Ix2};
125
126    #[test]
127    fn introspect_basics() {
128        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
129            .unwrap();
130
131        assert_eq!(arr.ndim(), 2);
132        assert_eq!(arr.size(), 6);
133        assert_eq!(arr.shape(), &[2, 3]);
134        assert_eq!(arr.itemsize(), 8); // f64 = 8 bytes
135        assert_eq!(arr.nbytes(), 48); // 6 * 8
136        assert_eq!(arr.dtype(), DType::F64);
137        assert!(!arr.is_empty());
138    }
139
140    #[test]
141    fn introspect_empty() {
142        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([0]), vec![]).unwrap();
143        assert!(arr.is_empty());
144        assert_eq!(arr.size(), 0);
145        assert_eq!(arr.nbytes(), 0);
146    }
147
148    #[test]
149    fn flags_owned() {
150        let arr = Array::<f64, Ix2>::zeros(Ix2::new([3, 4])).unwrap();
151        let f = arr.flags();
152        assert!(f.c_contiguous);
153        assert!(f.owndata);
154        assert!(f.writeable);
155        assert!(f.aligned, "owned arrays go through Vec<T> — always aligned");
156    }
157
158    #[test]
159    fn flags_owned_aligned_for_various_dtypes() {
160        // #345: owned arrays of every supported dtype must report
161        // aligned=true. Vec<T>'s allocator guarantees this.
162        let f64_arr = Array::<f64, Ix1>::zeros(Ix1::new([8])).unwrap();
163        assert!(f64_arr.flags().aligned);
164        let i32_arr = Array::<i32, Ix1>::zeros(Ix1::new([8])).unwrap();
165        assert!(i32_arr.flags().aligned);
166        let u8_arr = Array::<u8, Ix1>::zeros(Ix1::new([8])).unwrap();
167        assert!(u8_arr.flags().aligned);
168    }
169
170    #[test]
171    fn transpose_view() {
172        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
173            .unwrap();
174        let t = arr.t();
175        assert_eq!(t.shape(), &[3, 2]);
176        assert_eq!(t.size(), 6);
177    }
178
179    #[test]
180    fn copy_is_independent() {
181        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
182        let copy = arr.copy();
183        assert_eq!(copy.as_slice().unwrap(), arr.as_slice().unwrap());
184        assert_ne!(copy.as_ptr(), arr.as_ptr());
185    }
186
187    #[test]
188    fn to_vec_flat() {
189        let arr = Array::<i32, Ix2>::from_vec(Ix2::new([2, 2]), vec![1, 2, 3, 4]).unwrap();
190        assert_eq!(arr.to_vec_flat(), vec![1, 2, 3, 4]);
191    }
192
193    #[test]
194    fn to_bytes_contiguous() {
195        let arr = Array::<u8, Ix1>::from_vec(Ix1::new([4]), vec![0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
196        let bytes = arr.to_bytes().unwrap();
197        assert_eq!(bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
198    }
199
200    #[test]
201    fn view_introspection() {
202        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
203            .unwrap();
204        let v = arr.view();
205        assert_eq!(v.itemsize(), 8);
206        assert_eq!(v.nbytes(), 48);
207        assert_eq!(v.dtype(), DType::F64);
208    }
209
210    #[test]
211    fn view_transpose() {
212        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
213            .unwrap();
214        let v = arr.view();
215        let vt = v.t();
216        assert_eq!(vt.shape(), &[3, 2]);
217    }
218}