use std::{
marker::PhantomData,
mem,
os::raw::{c_int, c_void},
ptr, slice,
};
use ndarray::{
Array, ArrayBase, ArrayView, ArrayViewMut, Axis, Data, Dim, Dimension, IntoDimension, Ix0, Ix1,
Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, RawArrayView, RawArrayViewMut, RawData, ShapeBuilder,
StrideShape,
};
use num_traits::AsPrimitive;
use pyo3::{
ffi, pyobject_native_type_named, types::PyModule, AsPyPointer, FromPyObject, IntoPy, Py, PyAny,
PyClassInitializer, PyDowncastError, PyErr, PyNativeType, PyObject, PyResult, PyTypeInfo,
Python, ToPyObject,
};
use crate::borrow::{PyReadonlyArray, PyReadwriteArray};
use crate::cold;
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
use crate::dtype::{Element, PyArrayDescr};
use crate::error::{
BorrowError, DimensionalityError, FromVecError, IgnoreError, NotContiguousError, TypeError,
DIMENSIONALITY_MISMATCH_ERR, MAX_DIMENSIONALITY_ERR,
};
use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API};
use crate::slice_container::PySliceContainer;
#[repr(transparent)]
pub struct PyArray<T, D>(PyAny, PhantomData<T>, PhantomData<D>);
pub type PyArray0<T> = PyArray<T, Ix0>;
pub type PyArray1<T> = PyArray<T, Ix1>;
pub type PyArray2<T> = PyArray<T, Ix2>;
pub type PyArray3<T> = PyArray<T, Ix3>;
pub type PyArray4<T> = PyArray<T, Ix4>;
pub type PyArray5<T> = PyArray<T, Ix5>;
pub type PyArray6<T> = PyArray<T, Ix6>;
pub type PyArrayDyn<T> = PyArray<T, IxDyn>;
pub fn get_array_module(py: Python<'_>) -> PyResult<&PyModule> {
PyModule::import(py, npyffi::array::MOD_NAME)
}
unsafe impl<T: Element, D: Dimension> PyTypeInfo for PyArray<T, D> {
type AsRefTarget = Self;
const NAME: &'static str = "PyArray<T, D>";
const MODULE: Option<&'static str> = Some("numpy");
fn type_object_raw(py: Python) -> *mut ffi::PyTypeObject {
unsafe { npyffi::PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type) }
}
fn is_type_of(ob: &PyAny) -> bool {
Self::extract::<IgnoreError>(ob).is_ok()
}
}
pyobject_native_type_named!(PyArray<T, D>; T; D);
impl<T, D> IntoPy<PyObject> for PyArray<T, D> {
fn into_py(self, py: Python<'_>) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl<'py, T: Element, D: Dimension> FromPyObject<'py> for &'py PyArray<T, D> {
fn extract(ob: &'py PyAny) -> PyResult<Self> {
PyArray::extract(ob)
}
}
impl<T, D> PyArray<T, D> {
#[inline]
pub fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject {
self.as_ptr() as _
}
pub fn dtype(&self) -> &PyArrayDescr {
unsafe {
let descr_ptr = (*self.as_array_ptr()).descr;
self.py().from_borrowed_ptr(descr_ptr as _)
}
}
#[inline(always)]
pub(crate) fn check_flags(&self, flags: c_int) -> bool {
unsafe { (*self.as_array_ptr()).flags & flags != 0 }
}
pub fn is_contiguous(&self) -> bool {
self.check_flags(npyffi::NPY_ARRAY_C_CONTIGUOUS | npyffi::NPY_ARRAY_F_CONTIGUOUS)
}
pub fn is_fortran_contiguous(&self) -> bool {
self.check_flags(npyffi::NPY_ARRAY_F_CONTIGUOUS)
}
pub fn is_c_contiguous(&self) -> bool {
self.check_flags(npyffi::NPY_ARRAY_C_CONTIGUOUS)
}
pub fn to_owned(&self) -> Py<Self> {
unsafe { Py::from_borrowed_ptr(self.py(), self.as_ptr()) }
}
pub unsafe fn from_owned_ptr<'py>(py: Python<'py>, ptr: *mut ffi::PyObject) -> &'py Self {
py.from_owned_ptr(ptr)
}
pub unsafe fn from_borrowed_ptr<'py>(py: Python<'py>, ptr: *mut ffi::PyObject) -> &'py Self {
py.from_borrowed_ptr(ptr)
}
#[inline]
pub fn ndim(&self) -> usize {
unsafe { (*self.as_array_ptr()).nd as usize }
}
#[inline]
pub fn strides(&self) -> &[isize] {
let n = self.ndim();
if n == 0 {
cold();
return &[];
}
let ptr = self.as_array_ptr();
unsafe {
let p = (*ptr).strides;
slice::from_raw_parts(p, n)
}
}
#[inline]
pub fn shape(&self) -> &[usize] {
let n = self.ndim();
if n == 0 {
cold();
return &[];
}
let ptr = self.as_array_ptr();
unsafe {
let p = (*ptr).dimensions as *mut usize;
slice::from_raw_parts(p, n)
}
}
pub fn len(&self) -> usize {
self.shape().iter().product()
}
pub fn is_empty(&self) -> bool {
self.shape().iter().any(|dim| *dim == 0)
}
#[inline(always)]
pub fn data(&self) -> *mut T {
unsafe { (*self.as_array_ptr()).data as *mut _ }
}
}
impl<T: Element, D: Dimension> PyArray<T, D> {
fn extract<'py, E>(ob: &'py PyAny) -> Result<&'py Self, E>
where
E: From<PyDowncastError<'py>> + From<DimensionalityError> + From<TypeError<'py>>,
{
let array = unsafe {
if npyffi::PyArray_Check(ob.py(), ob.as_ptr()) == 0 {
return Err(PyDowncastError::new(ob, Self::NAME).into());
}
&*(ob as *const PyAny as *const Self)
};
let src_ndim = array.ndim();
if let Some(dst_ndim) = D::NDIM {
if src_ndim != dst_ndim {
return Err(DimensionalityError::new(src_ndim, dst_ndim).into());
}
}
let src_dtype = array.dtype();
let dst_dtype = T::get_dtype(ob.py());
if !src_dtype.is_equiv_to(dst_dtype) {
return Err(TypeError::new(src_dtype, dst_dtype).into());
}
Ok(array)
}
#[inline(always)]
pub fn dims(&self) -> D {
D::from_dimension(&Dim(self.shape())).expect(DIMENSIONALITY_MISMATCH_ERR)
}
pub unsafe fn new<ID>(py: Python, dims: ID, is_fortran: bool) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let flags = c_int::from(is_fortran);
Self::new_uninit(py, dims, ptr::null_mut(), flags)
}
pub(crate) unsafe fn new_uninit<ID>(
py: Python,
dims: ID,
strides: *const npy_intp,
flag: c_int,
) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let mut dims = dims.into_dimension();
let ptr = PY_ARRAY_API.PyArray_NewFromDescr(
py,
PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type),
T::get_dtype(py).into_dtype_ptr(),
dims.ndim_cint(),
dims.as_dims_ptr(),
strides as *mut npy_intp, ptr::null_mut(), flag, ptr::null_mut(), );
Self::from_owned_ptr(py, ptr)
}
unsafe fn new_with_data<'py, ID>(
py: Python<'py>,
dims: ID,
strides: *const npy_intp,
data_ptr: *const T,
container: *mut PyAny,
) -> &'py Self
where
ID: IntoDimension<Dim = D>,
{
let mut dims = dims.into_dimension();
let ptr = PY_ARRAY_API.PyArray_NewFromDescr(
py,
PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type),
T::get_dtype(py).into_dtype_ptr(),
dims.ndim_cint(),
dims.as_dims_ptr(),
strides as *mut npy_intp, data_ptr as *mut c_void, npyffi::NPY_ARRAY_WRITEABLE, ptr::null_mut(), );
PY_ARRAY_API.PyArray_SetBaseObject(
py,
ptr as *mut npyffi::PyArrayObject,
container as *mut ffi::PyObject,
);
Self::from_owned_ptr(py, ptr)
}
pub(crate) unsafe fn from_raw_parts<'py>(
py: Python<'py>,
dims: D,
strides: *const npy_intp,
data_ptr: *const T,
container: PySliceContainer,
) -> &'py Self {
let container = PyClassInitializer::from(container)
.create_cell(py)
.expect("Failed to create slice container");
Self::new_with_data(py, dims, strides, data_ptr, container as *mut PyAny)
}
pub unsafe fn borrow_from_array<'py, S>(
array: &ArrayBase<S, D>,
container: &'py PyAny,
) -> &'py Self
where
S: Data<Elem = T>,
{
let (strides, dims) = (array.npy_strides(), array.raw_dim());
let data_ptr = array.as_ptr();
let py = container.py();
mem::forget(container.to_object(py));
Self::new_with_data(
py,
dims,
strides.as_ptr(),
data_ptr,
container as *const PyAny as *mut PyAny,
)
}
pub fn zeros<ID>(py: Python, dims: ID, is_fortran: bool) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let mut dims = dims.into_dimension();
unsafe {
let ptr = PY_ARRAY_API.PyArray_Zeros(
py,
dims.ndim_cint(),
dims.as_dims_ptr(),
T::get_dtype(py).into_dtype_ptr(),
if is_fortran { -1 } else { 0 },
);
Self::from_owned_ptr(py, ptr)
}
}
pub unsafe fn as_slice(&self) -> Result<&[T], NotContiguousError> {
if self.is_contiguous() {
Ok(slice::from_raw_parts(self.data(), self.len()))
} else {
Err(NotContiguousError)
}
}
pub unsafe fn as_slice_mut(&self) -> Result<&mut [T], NotContiguousError> {
if self.is_contiguous() {
Ok(slice::from_raw_parts_mut(self.data(), self.len()))
} else {
Err(NotContiguousError)
}
}
pub fn from_owned_array<'py>(py: Python<'py>, mut arr: Array<T, D>) -> &'py Self {
let (strides, dims) = (arr.npy_strides(), arr.raw_dim());
let data_ptr = arr.as_mut_ptr();
unsafe {
Self::from_raw_parts(
py,
dims,
strides.as_ptr(),
data_ptr,
PySliceContainer::from(arr),
)
}
}
#[inline(always)]
pub unsafe fn get(&self, index: impl NpyIndex<Dim = D>) -> Option<&T> {
let ptr = self.get_raw(index)?;
Some(&*ptr)
}
#[inline(always)]
pub unsafe fn get_mut(&self, index: impl NpyIndex<Dim = D>) -> Option<&mut T> {
let ptr = self.get_raw(index)?;
Some(&mut *ptr)
}
#[inline(always)]
fn get_raw<Idx>(&self, index: Idx) -> Option<*mut T>
where
Idx: NpyIndex<Dim = D>,
{
let offset = index.get_checked::<T>(self.shape(), self.strides())?;
Some(unsafe { self.data().offset(offset) })
}
#[inline(always)]
pub unsafe fn uget<Idx>(&self, index: Idx) -> &T
where
Idx: NpyIndex<Dim = D>,
{
&*self.uget_raw(index)
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn uget_mut<Idx>(&self, index: Idx) -> &mut T
where
Idx: NpyIndex<Dim = D>,
{
&mut *self.uget_raw(index)
}
#[inline(always)]
pub unsafe fn uget_raw<Idx>(&self, index: Idx) -> *mut T
where
Idx: NpyIndex<Dim = D>,
{
let offset = index.get_unchecked::<T>(self.strides());
self.data().offset(offset) as *mut _
}
pub fn get_owned<Idx>(&self, index: Idx) -> Option<T>
where
Idx: NpyIndex<Dim = D>,
{
unsafe { self.get(index) }.cloned()
}
pub fn to_dyn(&self) -> &PyArray<T, IxDyn> {
unsafe { PyArray::from_borrowed_ptr(self.py(), self.as_ptr()) }
}
pub fn to_vec(&self) -> Result<Vec<T>, NotContiguousError> {
unsafe { self.as_slice() }.map(ToOwned::to_owned)
}
pub fn from_array<'py, S>(py: Python<'py>, arr: &ArrayBase<S, D>) -> &'py Self
where
S: Data<Elem = T>,
{
ToPyArray::to_pyarray(arr, py)
}
pub fn try_readonly(&self) -> Result<PyReadonlyArray<'_, T, D>, BorrowError> {
PyReadonlyArray::try_new(self)
}
pub fn readonly(&self) -> PyReadonlyArray<'_, T, D> {
self.try_readonly().unwrap()
}
pub fn try_readwrite(&self) -> Result<PyReadwriteArray<'_, T, D>, BorrowError> {
PyReadwriteArray::try_new(self)
}
pub fn readwrite(&self) -> PyReadwriteArray<'_, T, D> {
self.try_readwrite().unwrap()
}
fn as_view<S: RawData, F>(&self, from_shape_ptr: F) -> ArrayBase<S, D>
where
F: FnOnce(StrideShape<D>, *mut T) -> ArrayBase<S, D>,
{
fn inner<D: Dimension>(
shape: &[usize],
strides: &[isize],
itemsize: usize,
mut data_ptr: *mut u8,
) -> (StrideShape<D>, u32, *mut u8) {
let shape = D::from_dimension(&Dim(shape)).expect(DIMENSIONALITY_MISMATCH_ERR);
assert!(strides.len() <= 32, "{}", MAX_DIMENSIONALITY_ERR);
let mut new_strides = D::zeros(strides.len());
let mut inverted_axes = 0_u32;
for i in 0..strides.len() {
if strides[i] >= 0 {
new_strides[i] = strides[i] as usize / itemsize;
} else {
data_ptr = unsafe { data_ptr.offset(strides[i] * (shape[i] as isize - 1)) };
new_strides[i] = (-strides[i]) as usize / itemsize;
inverted_axes |= 1 << i;
}
}
(shape.strides(new_strides), inverted_axes, data_ptr)
}
let (shape, mut inverted_axes, data_ptr) = inner(
self.shape(),
self.strides(),
mem::size_of::<T>(),
self.data() as _,
);
let mut array = from_shape_ptr(shape, data_ptr as _);
while inverted_axes != 0 {
let axis = inverted_axes.trailing_zeros() as usize;
inverted_axes &= !(1 << axis);
array.invert_axis(Axis(axis));
}
array
}
pub unsafe fn as_array(&self) -> ArrayView<'_, T, D> {
self.as_view(|shape, ptr| ArrayView::from_shape_ptr(shape, ptr))
}
pub unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D> {
self.as_view(|shape, ptr| ArrayViewMut::from_shape_ptr(shape, ptr))
}
pub fn as_raw_array(&self) -> RawArrayView<T, D> {
self.as_view(|shape, ptr| unsafe { RawArrayView::from_shape_ptr(shape, ptr) })
}
pub fn as_raw_array_mut(&self) -> RawArrayViewMut<T, D> {
self.as_view(|shape, ptr| unsafe { RawArrayViewMut::from_shape_ptr(shape, ptr) })
}
pub fn to_owned_array(&self) -> Array<T, D> {
unsafe { self.as_array() }.to_owned()
}
}
#[cfg(feature = "nalgebra")]
impl<N, D> PyArray<N, D>
where
N: nalgebra::Scalar + Element,
D: Dimension,
{
fn try_as_matrix_shape_strides<R, C, RStride, CStride>(
&self,
) -> Option<((R, C), (RStride, CStride))>
where
R: nalgebra::Dim,
C: nalgebra::Dim,
RStride: nalgebra::Dim,
CStride: nalgebra::Dim,
{
let ndim = self.ndim();
let shape = self.shape();
let strides = self.strides();
if ndim != 1 && ndim != 2 {
return None;
}
if strides.iter().any(|strides| *strides < 0) {
return None;
}
let rows = shape[0];
let cols = *shape.get(1).unwrap_or(&1);
if R::try_to_usize().map(|expected| rows == expected) == Some(false) {
return None;
}
if C::try_to_usize().map(|expected| cols == expected) == Some(false) {
return None;
}
let row_stride = strides[0] as usize / mem::size_of::<N>();
let col_stride = strides
.get(1)
.map_or(rows, |stride| *stride as usize / mem::size_of::<N>());
if RStride::try_to_usize().map(|expected| row_stride == expected) == Some(false) {
return None;
}
if CStride::try_to_usize().map(|expected| col_stride == expected) == Some(false) {
return None;
}
let shape = (R::from_usize(rows), C::from_usize(cols));
let strides = (
RStride::from_usize(row_stride),
CStride::from_usize(col_stride),
);
Some((shape, strides))
}
#[doc(alias = "nalgebra")]
pub unsafe fn try_as_matrix<R, C, RStride, CStride>(
&self,
) -> Option<nalgebra::MatrixView<N, R, C, RStride, CStride>>
where
R: nalgebra::Dim,
C: nalgebra::Dim,
RStride: nalgebra::Dim,
CStride: nalgebra::Dim,
{
let (shape, strides) = self.try_as_matrix_shape_strides()?;
let storage = nalgebra::ViewStorage::from_raw_parts(self.data(), shape, strides);
Some(nalgebra::Matrix::from_data(storage))
}
#[doc(alias = "nalgebra")]
pub unsafe fn try_as_matrix_mut<R, C, RStride, CStride>(
&self,
) -> Option<nalgebra::MatrixViewMut<N, R, C, RStride, CStride>>
where
R: nalgebra::Dim,
C: nalgebra::Dim,
RStride: nalgebra::Dim,
CStride: nalgebra::Dim,
{
let (shape, strides) = self.try_as_matrix_shape_strides()?;
let storage = nalgebra::ViewStorageMut::from_raw_parts(self.data(), shape, strides);
Some(nalgebra::Matrix::from_data(storage))
}
}
impl<D: Dimension> PyArray<PyObject, D> {
pub fn from_owned_object_array<'py, T>(py: Python<'py>, mut arr: Array<Py<T>, D>) -> &'py Self {
let (strides, dims) = (arr.npy_strides(), arr.raw_dim());
let data_ptr = arr.as_mut_ptr() as *const PyObject;
unsafe {
Self::from_raw_parts(
py,
dims,
strides.as_ptr(),
data_ptr,
PySliceContainer::from(arr),
)
}
}
}
impl<T: Copy + Element> PyArray<T, Ix0> {
pub fn item(&self) -> T {
unsafe { *self.data() }
}
}
impl<T: Element> PyArray<T, Ix1> {
pub fn from_slice<'py>(py: Python<'py>, slice: &[T]) -> &'py Self {
unsafe {
let array = PyArray::new(py, [slice.len()], false);
let mut data_ptr = array.data();
clone_elements(slice, &mut data_ptr);
array
}
}
#[inline(always)]
pub fn from_vec<'py>(py: Python<'py>, vec: Vec<T>) -> &'py Self {
vec.into_pyarray(py)
}
#[deprecated(
since = "0.17.0",
note = "`from_exact_iter` is deprecated as it does not provide any benefit over `from_iter`."
)]
#[inline(always)]
pub fn from_exact_iter<I>(py: Python<'_>, iter: I) -> &Self
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
Self::from_iter(py, iter)
}
pub fn from_iter<I>(py: Python<'_>, iter: I) -> &Self
where
I: IntoIterator<Item = T>,
{
let data = iter.into_iter().collect::<Vec<_>>();
data.into_pyarray(py)
}
}
impl<T: Element> PyArray<T, Ix2> {
pub fn from_vec2<'py>(py: Python<'py>, v: &[Vec<T>]) -> Result<&'py Self, FromVecError> {
let len2 = v.first().map_or(0, |v| v.len());
let dims = [v.len(), len2];
unsafe {
let array = Self::new(py, dims, false);
let mut data_ptr = array.data();
for v in v {
if v.len() != len2 {
cold();
return Err(FromVecError::new(v.len(), len2));
}
clone_elements(v, &mut data_ptr);
}
Ok(array)
}
}
}
impl<T: Element> PyArray<T, Ix3> {
pub fn from_vec3<'py>(py: Python<'py>, v: &[Vec<Vec<T>>]) -> Result<&'py Self, FromVecError> {
let len2 = v.first().map_or(0, |v| v.len());
let len3 = v.first().map_or(0, |v| v.first().map_or(0, |v| v.len()));
let dims = [v.len(), len2, len3];
unsafe {
let array = Self::new(py, dims, false);
let mut data_ptr = array.data();
for v in v {
if v.len() != len2 {
cold();
return Err(FromVecError::new(v.len(), len2));
}
for v in v {
if v.len() != len3 {
cold();
return Err(FromVecError::new(v.len(), len3));
}
clone_elements(v, &mut data_ptr);
}
}
Ok(array)
}
}
}
impl<T: Element, D> PyArray<T, D> {
pub fn copy_to<U: Element>(&self, other: &PyArray<U, D>) -> PyResult<()> {
let self_ptr = self.as_array_ptr();
let other_ptr = other.as_array_ptr();
let result = unsafe { PY_ARRAY_API.PyArray_CopyInto(self.py(), other_ptr, self_ptr) };
if result != -1 {
Ok(())
} else {
Err(PyErr::fetch(self.py()))
}
}
pub fn cast<'py, U: Element>(&'py self, is_fortran: bool) -> PyResult<&'py PyArray<U, D>> {
let ptr = unsafe {
PY_ARRAY_API.PyArray_CastToType(
self.py(),
self.as_array_ptr(),
U::get_dtype(self.py()).into_dtype_ptr(),
if is_fortran { -1 } else { 0 },
)
};
if !ptr.is_null() {
Ok(unsafe { PyArray::<U, D>::from_owned_ptr(self.py(), ptr) })
} else {
Err(PyErr::fetch(self.py()))
}
}
pub fn reshape_with_order<'py, ID: IntoDimension>(
&'py self,
dims: ID,
order: NPY_ORDER,
) -> PyResult<&'py PyArray<T, ID::Dim>> {
let mut dims = dims.into_dimension();
let mut dims = dims.to_npy_dims();
let ptr = unsafe {
PY_ARRAY_API.PyArray_Newshape(
self.py(),
self.as_array_ptr(),
&mut dims as *mut npyffi::PyArray_Dims,
order,
)
};
if !ptr.is_null() {
Ok(unsafe { PyArray::<T, ID::Dim>::from_owned_ptr(self.py(), ptr) })
} else {
Err(PyErr::fetch(self.py()))
}
}
#[inline(always)]
pub fn reshape<'py, ID: IntoDimension>(
&'py self,
dims: ID,
) -> PyResult<&'py PyArray<T, ID::Dim>> {
self.reshape_with_order(dims, NPY_ORDER::NPY_ANYORDER)
}
pub unsafe fn resize<ID: IntoDimension>(&self, dims: ID) -> PyResult<()> {
let mut dims = dims.into_dimension();
let mut dims = dims.to_npy_dims();
let res = PY_ARRAY_API.PyArray_Resize(
self.py(),
self.as_array_ptr(),
&mut dims as *mut npyffi::PyArray_Dims,
1,
NPY_ORDER::NPY_ANYORDER,
);
if !res.is_null() {
Ok(())
} else {
Err(PyErr::fetch(self.py()))
}
}
}
impl<T: Element + AsPrimitive<f64>> PyArray<T, Ix1> {
pub fn arange(py: Python, start: T, stop: T, step: T) -> &Self {
unsafe {
let ptr = PY_ARRAY_API.PyArray_Arange(
py,
start.as_(),
stop.as_(),
step.as_(),
T::get_dtype(py).num(),
);
Self::from_owned_ptr(py, ptr)
}
}
}
unsafe fn clone_elements<T: Element>(elems: &[T], data_ptr: &mut *mut T) {
if T::IS_COPY {
ptr::copy_nonoverlapping(elems.as_ptr(), *data_ptr, elems.len());
*data_ptr = data_ptr.add(elems.len());
} else {
for elem in elems {
data_ptr.write(elem.clone());
*data_ptr = data_ptr.add(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
use pyo3::{py_run, types::PyList};
#[test]
fn test_dyn_to_owned_array() {
Python::with_gil(|py| {
let array = PyArray::from_vec2(py, &[vec![1, 2], vec![3, 4]])
.unwrap()
.to_dyn()
.to_owned_array();
assert_eq!(array, array![[1, 2], [3, 4]].into_dyn());
});
}
#[test]
fn test_hasobject_flag() {
Python::with_gil(|py| {
let array: &PyArray<PyObject, _> =
PyArray1::from_slice(py, &[PyList::empty(py).into()]);
py_run!(py, array, "assert array.dtype.hasobject");
});
}
}