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 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    pub fn copy(&self) -> Self {
46        Self {
47            inner: self.inner.clone(),
48            dim: self.dim.clone(),
49        }
50    }
51
52    /// Convert to a flat `Vec<T>` in logical (row-major) order.
53    pub fn to_vec_flat(&self) -> Vec<T> {
54        self.inner.iter().cloned().collect()
55    }
56
57    /// Return the raw bytes of the array data.
58    ///
59    /// Only succeeds if the array is contiguous; returns an error otherwise.
60    pub fn to_bytes(&self) -> FerrayResult<&[u8]> {
61        let slice = self.inner.as_slice().ok_or_else(|| {
62            FerrayError::invalid_value("array is not contiguous; cannot produce byte slice")
63        })?;
64        let ptr = slice.as_ptr() as *const u8;
65        let len = std::mem::size_of_val(slice);
66        // SAFETY: the slice is contiguous and alive for 'self lifetime;
67        // reinterpreting as bytes is always safe for Copy-like types.
68        Ok(unsafe { std::slice::from_raw_parts(ptr, len) })
69    }
70
71    /// Return flags describing memory properties.
72    pub fn flags(&self) -> ArrayFlags {
73        let layout = self.layout();
74        ArrayFlags {
75            c_contiguous: layout.is_c_contiguous(),
76            f_contiguous: layout.is_f_contiguous(),
77            owndata: true,
78            writeable: true,
79        }
80    }
81}
82
83// ---------------------------------------------------------------------------
84// REQ-35: Core introspection for ArrayView<'a, T, D>
85// ---------------------------------------------------------------------------
86
87impl<T: Element, D: Dimension> ArrayView<'_, T, D> {
88    /// Size in bytes of a single element.
89    #[inline]
90    pub fn itemsize(&self) -> usize {
91        std::mem::size_of::<T>()
92    }
93
94    /// Total size in bytes of all elements.
95    #[inline]
96    pub fn nbytes(&self) -> usize {
97        self.size() * self.itemsize()
98    }
99
100    /// Runtime dtype tag.
101    #[inline]
102    pub fn dtype(&self) -> DType {
103        T::dtype()
104    }
105
106    /// Transposed view (zero-copy).
107    pub fn t(&self) -> ArrayView<'_, T, D> {
108        let transposed = self.inner.clone().reversed_axes();
109        ArrayView::from_ndarray(transposed)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::dimension::{Ix1, Ix2};
117
118    #[test]
119    fn introspect_basics() {
120        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
121            .unwrap();
122
123        assert_eq!(arr.ndim(), 2);
124        assert_eq!(arr.size(), 6);
125        assert_eq!(arr.shape(), &[2, 3]);
126        assert_eq!(arr.itemsize(), 8); // f64 = 8 bytes
127        assert_eq!(arr.nbytes(), 48); // 6 * 8
128        assert_eq!(arr.dtype(), DType::F64);
129        assert!(!arr.is_empty());
130    }
131
132    #[test]
133    fn introspect_empty() {
134        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([0]), vec![]).unwrap();
135        assert!(arr.is_empty());
136        assert_eq!(arr.size(), 0);
137        assert_eq!(arr.nbytes(), 0);
138    }
139
140    #[test]
141    fn flags_owned() {
142        let arr = Array::<f64, Ix2>::zeros(Ix2::new([3, 4])).unwrap();
143        let f = arr.flags();
144        assert!(f.c_contiguous);
145        assert!(f.owndata);
146        assert!(f.writeable);
147    }
148
149    #[test]
150    fn transpose_view() {
151        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
152            .unwrap();
153        let t = arr.t();
154        assert_eq!(t.shape(), &[3, 2]);
155        assert_eq!(t.size(), 6);
156    }
157
158    #[test]
159    fn copy_is_independent() {
160        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
161        let copy = arr.copy();
162        assert_eq!(copy.as_slice().unwrap(), arr.as_slice().unwrap());
163        assert_ne!(copy.as_ptr(), arr.as_ptr());
164    }
165
166    #[test]
167    fn to_vec_flat() {
168        let arr = Array::<i32, Ix2>::from_vec(Ix2::new([2, 2]), vec![1, 2, 3, 4]).unwrap();
169        assert_eq!(arr.to_vec_flat(), vec![1, 2, 3, 4]);
170    }
171
172    #[test]
173    fn to_bytes_contiguous() {
174        let arr = Array::<u8, Ix1>::from_vec(Ix1::new([4]), vec![0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
175        let bytes = arr.to_bytes().unwrap();
176        assert_eq!(bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
177    }
178
179    #[test]
180    fn view_introspection() {
181        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
182            .unwrap();
183        let v = arr.view();
184        assert_eq!(v.itemsize(), 8);
185        assert_eq!(v.nbytes(), 48);
186        assert_eq!(v.dtype(), DType::F64);
187    }
188
189    #[test]
190    fn view_transpose() {
191        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
192            .unwrap();
193        let v = arr.view();
194        let vt = v.t();
195        assert_eq!(vt.shape(), &[3, 2]);
196    }
197}