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        }
85    }
86}
87
88// ---------------------------------------------------------------------------
89// REQ-35: Core introspection for ArrayView<'a, T, D>
90// ---------------------------------------------------------------------------
91
92impl<T: Element, D: Dimension> ArrayView<'_, T, D> {
93    /// Size in bytes of a single element.
94    #[inline]
95    pub const fn itemsize(&self) -> usize {
96        std::mem::size_of::<T>()
97    }
98
99    /// Total size in bytes of all elements.
100    #[inline]
101    pub fn nbytes(&self) -> usize {
102        self.size() * self.itemsize()
103    }
104
105    /// Runtime dtype tag.
106    #[inline]
107    pub fn dtype(&self) -> DType {
108        T::dtype()
109    }
110
111    /// Transposed view (zero-copy).
112    pub fn t(&self) -> ArrayView<'_, T, D> {
113        let transposed = self.inner.clone().reversed_axes();
114        ArrayView::from_ndarray(transposed)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::dimension::{Ix1, Ix2};
122
123    #[test]
124    fn introspect_basics() {
125        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
126            .unwrap();
127
128        assert_eq!(arr.ndim(), 2);
129        assert_eq!(arr.size(), 6);
130        assert_eq!(arr.shape(), &[2, 3]);
131        assert_eq!(arr.itemsize(), 8); // f64 = 8 bytes
132        assert_eq!(arr.nbytes(), 48); // 6 * 8
133        assert_eq!(arr.dtype(), DType::F64);
134        assert!(!arr.is_empty());
135    }
136
137    #[test]
138    fn introspect_empty() {
139        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([0]), vec![]).unwrap();
140        assert!(arr.is_empty());
141        assert_eq!(arr.size(), 0);
142        assert_eq!(arr.nbytes(), 0);
143    }
144
145    #[test]
146    fn flags_owned() {
147        let arr = Array::<f64, Ix2>::zeros(Ix2::new([3, 4])).unwrap();
148        let f = arr.flags();
149        assert!(f.c_contiguous);
150        assert!(f.owndata);
151        assert!(f.writeable);
152    }
153
154    #[test]
155    fn transpose_view() {
156        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
157            .unwrap();
158        let t = arr.t();
159        assert_eq!(t.shape(), &[3, 2]);
160        assert_eq!(t.size(), 6);
161    }
162
163    #[test]
164    fn copy_is_independent() {
165        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
166        let copy = arr.copy();
167        assert_eq!(copy.as_slice().unwrap(), arr.as_slice().unwrap());
168        assert_ne!(copy.as_ptr(), arr.as_ptr());
169    }
170
171    #[test]
172    fn to_vec_flat() {
173        let arr = Array::<i32, Ix2>::from_vec(Ix2::new([2, 2]), vec![1, 2, 3, 4]).unwrap();
174        assert_eq!(arr.to_vec_flat(), vec![1, 2, 3, 4]);
175    }
176
177    #[test]
178    fn to_bytes_contiguous() {
179        let arr = Array::<u8, Ix1>::from_vec(Ix1::new([4]), vec![0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
180        let bytes = arr.to_bytes().unwrap();
181        assert_eq!(bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
182    }
183
184    #[test]
185    fn view_introspection() {
186        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
187            .unwrap();
188        let v = arr.view();
189        assert_eq!(v.itemsize(), 8);
190        assert_eq!(v.nbytes(), 48);
191        assert_eq!(v.dtype(), DType::F64);
192    }
193
194    #[test]
195    fn view_transpose() {
196        let arr = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
197            .unwrap();
198        let v = arr.view();
199        let vt = v.t();
200        assert_eq!(vt.shape(), &[3, 2]);
201    }
202}