1#![deny(missing_docs, missing_debug_implementations)]
4
5#[cfg(test)]
6mod test;
7
8use ndarray::{Array, ArrayView, Axis, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn};
9use numpy::{
10 ndarray::Dimension,
11 pyo3::{
12 exceptions::PyValueError, types::PyAnyMethods, Borrowed, Bound, FromPyObject, PyAny, PyErr,
13 PyResult, Python,
14 },
15 Element, IntoPyArray, PyArray, PyArrayMethods, PyReadonlyArray,
16};
17use std::fmt::Debug;
18
19#[derive(Debug)]
21pub struct PyArrayLike<'py, T, D>(ArrayLike<'py, T, D>)
22where
23 T: Element,
24 D: Dimension;
25
26enum ArrayLike<'py, T, D>
27where
28 T: Element,
29 D: Dimension,
30{
31 PyRef(PyReadonlyArray<'py, T, D>),
32 Owned(Array<T, D>, Python<'py>),
33}
34
35impl<'py, T, D> Debug for ArrayLike<'py, T, D>
36where
37 T: Element + Debug,
38 D: Dimension,
39{
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Self::PyRef(py_array) => f.debug_tuple("PyRef").field(py_array).finish(),
43 Self::Owned(array, _) => f.debug_tuple("Owned").field(array).finish(),
44 }
45 }
46}
47
48impl<'py, T, D> PyArrayLike<'py, T, D>
49where
50 T: Element,
51 D: Dimension,
52{
53 pub fn into_owned_array(self) -> Array<T, D> {
55 match self.0 {
56 ArrayLike::PyRef(py_array) => py_array.to_owned_array(),
57 ArrayLike::Owned(array, _) => array,
58 }
59 }
60
61 pub fn into_pyarray(self) -> PyReadonlyArray<'py, T, D> {
63 match self.0 {
64 ArrayLike::PyRef(py_array) => py_array,
65 ArrayLike::Owned(array, py) => array.into_pyarray(py).readonly(),
66 }
67 }
68
69 pub fn view<'s>(&'s self) -> ArrayView<'s, T, D> {
71 match &self.0 {
72 ArrayLike::PyRef(py_array) => py_array.as_array(),
73 ArrayLike::Owned(array, _) => array.view(),
74 }
75 }
76
77 pub fn as_slice(&self) -> Option<&[T]> {
79 match &self.0 {
80 ArrayLike::PyRef(py_array) => py_array.as_slice().ok(),
81 ArrayLike::Owned(array, _) => array.as_slice(),
82 }
83 }
84
85 pub fn dim(&self) -> D::Pattern {
87 match &self.0 {
88 ArrayLike::PyRef(py_array) => py_array.dims().into_pattern(),
89 ArrayLike::Owned(array, _) => array.dim(),
90 }
91 }
92}
93
94impl<'py, T, D> From<PyArrayLike<'py, T, D>> for PyReadonlyArray<'py, T, D>
95where
96 T: Element,
97 D: Dimension,
98{
99 fn from(value: PyArrayLike<'py, T, D>) -> Self {
100 value.into_pyarray()
101 }
102}
103
104impl<T, D> From<PyArrayLike<'_, T, D>> for Array<T, D>
105where
106 T: Element,
107 D: Dimension,
108{
109 fn from(value: PyArrayLike<T, D>) -> Self {
110 value.into_owned_array()
111 }
112}
113
114impl<'py, T, D> PyArrayLike<'py, T, D>
115where
116 T: Clone + Element + 'static + for<'a> FromPyObject<'a, 'py>,
117 D: Dimension + 'static,
118{
119 fn from_python(ob: &Bound<'py, PyAny>) -> Option<Self> {
120 if let Ok(array) = ob.cast::<PyArray<T, D>>() {
121 return Some(PyArrayLike(ArrayLike::PyRef(array.readonly())));
122 }
123
124 if matches!(D::NDIM, None | Some(0)) {
125 if let Ok(value) = ob.extract::<T>() {
126 let res = Array::from_elem((), value).into_dimensionality().ok()?;
127 return Some(PyArrayLike(ArrayLike::Owned(res, ob.py())));
128 }
129 }
130
131 if matches!(D::NDIM, None | Some(1)) {
132 if let Ok(array) = ob.extract::<Vec<T>>() {
133 let res = Array::from_vec(array).into_dimensionality().ok()?;
134 return Some(PyArrayLike(ArrayLike::Owned(res, ob.py())));
135 }
136 }
137
138 let sub_arrays = ob
139 .try_iter()
140 .ok()?
141 .map(|item| {
142 item.ok()
143 .and_then(|ob| <PyArrayLike<T, D::Smaller>>::from_python(&ob))
144 })
145 .collect::<Option<Vec<_>>>()?;
146 let sub_array_views = sub_arrays.iter().map(|x| x.view()).collect::<Vec<_>>();
147 let array = ndarray::stack(Axis(0), &sub_array_views)
148 .ok()?
149 .into_dimensionality()
150 .ok()?;
151 Some(PyArrayLike(ArrayLike::Owned(array, ob.py())))
152 }
153}
154
155impl<'py, T, D> FromPyObject<'_, 'py> for PyArrayLike<'py, T, D>
156where
157 T: Clone + Element + 'static + for<'a> FromPyObject<'a, 'py>,
158 D: Dimension + 'static,
159{
160 type Error = PyErr;
161
162 fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult<Self> {
163 Self::from_python(&ob).ok_or_else(|| {
164 let dtype = T::get_dtype(ob.py());
165 let err_text = match D::NDIM {
166 Some(dim) => format!("Expected an array like of dimension {} containing elements which can be safely casted to {}.", dim, dtype),
167 None => format!("Expected an array like of arbitrary dimension containing elements which can be safely casted to {}.", dtype)
168 };
169 PyValueError::new_err(err_text)})
170 }
171}
172
173pub type PyArrayLike0<'py, T> = PyArrayLike<'py, T, Ix0>;
175pub type PyArrayLike1<'py, T> = PyArrayLike<'py, T, Ix1>;
177pub type PyArrayLike2<'py, T> = PyArrayLike<'py, T, Ix2>;
179pub type PyArrayLike3<'py, T> = PyArrayLike<'py, T, Ix3>;
181pub type PyArrayLike4<'py, T> = PyArrayLike<'py, T, Ix4>;
183pub type PyArrayLike5<'py, T> = PyArrayLike<'py, T, Ix5>;
185pub type PyArrayLike6<'py, T> = PyArrayLike<'py, T, Ix6>;
187pub type PyArrayLikeDyn<'py, T> = PyArrayLike<'py, T, IxDyn>;