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