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, Shape, ShapeBuilder,
StrideShape,
};
use num_traits::AsPrimitive;
use pyo3::{
ffi, pyobject_native_type_named, type_object, types::PyModule, AsPyPointer, FromPyObject,
IntoPy, Py, PyAny, PyDowncastError, PyErr, PyNativeType, PyObject, PyResult, PyTypeInfo,
Python, ToPyObject,
};
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
use crate::dtype::{Element, PyArrayDescr};
use crate::error::{DimensionalityError, FromVecError, NotContiguousError, TypeError};
use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API};
#[allow(deprecated)]
use crate::npyiter::{NpySingleIter, NpySingleIterBuilder, ReadWrite};
use crate::readonly::PyReadonlyArray;
use crate::slice_container::PySliceContainer;
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, D> type_object::PyLayout<PyArray<T, D>> for npyffi::PyArrayObject {}
impl<T, D> type_object::PySizedLayout<PyArray<T, D>> for npyffi::PyArrayObject {}
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");
#[inline]
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(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> {
let array = unsafe {
if npyffi::PyArray_Check(ob.py(), ob.as_ptr()) == 0 {
return Err(PyDowncastError::new(ob, "PyArray<T, D>").into());
}
&*(ob as *const PyAny as *const PyArray<T, D>)
};
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());
}
let src_ndim = array.shape().len();
if let Some(dst_ndim) = D::NDIM {
if src_ndim != dst_ndim {
return Err(DimensionalityError::new(src_ndim, dst_ndim).into());
}
}
Ok(array)
}
}
impl<T, D> PyArray<T, D> {
pub fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject {
self.as_ptr() as _
}
pub fn dtype(&self) -> &PyArrayDescr {
let descr_ptr = unsafe { (*self.as_array_ptr()).descr };
unsafe { pyo3::FromPyPointer::from_borrowed_ptr(self.py(), descr_ptr as _) }
}
#[inline(always)]
fn check_flag(&self, flag: c_int) -> bool {
unsafe { *self.as_array_ptr() }.flags & flag == flag
}
#[inline(always)]
pub(crate) fn get_flag(&self) -> c_int {
unsafe { *self.as_array_ptr() }.flags
}
pub fn readonly(&self) -> PyReadonlyArray<T, D> {
self.into()
}
pub fn is_contiguous(&self) -> bool {
self.check_flag(npyffi::NPY_ARRAY_C_CONTIGUOUS)
| self.check_flag(npyffi::NPY_ARRAY_F_CONTIGUOUS)
}
pub fn is_fortran_contiguous(&self) -> bool {
self.check_flag(npyffi::NPY_ARRAY_F_CONTIGUOUS)
}
pub fn is_c_contiguous(&self) -> bool {
self.check_flag(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: Python<'_>, ptr: *mut ffi::PyObject) -> &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)
}
pub fn ndim(&self) -> usize {
let ptr = self.as_array_ptr();
unsafe { (*ptr).nd as usize }
}
pub fn strides(&self) -> &[isize] {
let n = self.ndim();
let ptr = self.as_array_ptr();
unsafe {
let p = (*ptr).strides;
slice::from_raw_parts(p, n)
}
}
pub fn shape(&self) -> &[usize] {
let n = self.ndim();
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.len() == 0
}
pub(crate) unsafe fn data(&self) -> *mut T {
let ptr = self.as_array_ptr();
(*ptr).data as *mut _
}
pub(crate) unsafe fn copy_ptr(&self, other: *const T, len: usize) {
ptr::copy_nonoverlapping(other, self.data(), len)
}
}
struct InvertedAxes(u32);
impl InvertedAxes {
fn new(len: usize) -> Self {
assert!(len <= 32, "Only dimensionalities of up to 32 are supported");
Self(0)
}
fn push(&mut self, axis: usize) {
debug_assert!(axis < 32);
self.0 |= 1 << axis;
}
fn invert<S: RawData, D: Dimension>(mut self, array: &mut ArrayBase<S, D>) {
while self.0 != 0 {
let axis = self.0.trailing_zeros() as usize;
self.0 &= !(1 << axis);
array.invert_axis(Axis(axis));
}
}
}
impl<T: Element, D: Dimension> PyArray<T, D> {
#[inline(always)]
pub fn dims(&self) -> D {
D::from_dimension(&Dim(self.shape())).expect("mismatching dimensions")
}
fn ndarray_shape_ptr(&self) -> (StrideShape<D>, *mut T, InvertedAxes) {
let shape = self.shape();
let strides = self.strides();
let mut new_strides = D::zeros(strides.len());
let mut data_ptr = unsafe { self.data() };
let mut inverted_axes = InvertedAxes::new(strides.len());
for i in 0..strides.len() {
if strides[i] < 0 {
let offset = strides[i] * (shape[i] as isize - 1) / mem::size_of::<T>() as isize;
unsafe {
data_ptr = data_ptr.offset(offset);
}
new_strides[i] = (-strides[i]) as usize / mem::size_of::<T>();
inverted_axes.push(i);
} else {
new_strides[i] = strides[i] as usize / mem::size_of::<T>();
}
}
let shape = Shape::from(D::from_dimension(&Dim(shape)).expect("mismatching dimensions"));
let new_strides = D::from_dimension(&Dim(new_strides)).expect("mismatching dimensions");
(shape.strides(new_strides), data_ptr, inverted_axes)
}
pub unsafe fn new<ID>(py: Python, dims: ID, is_fortran: bool) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let flags = if is_fortran { 1 } else { 0 };
PyArray::new_(py, dims, ptr::null_mut(), flags)
}
pub(crate) unsafe fn new_<ID>(
py: Python,
dims: ID,
strides: *const npy_intp,
flag: c_int,
) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let 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 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, ID, C>(
py: Python<'py>,
dims: ID,
strides: *const npy_intp,
data_ptr: *const T,
container: C,
) -> &'py Self
where
ID: IntoDimension<Dim = D>,
PySliceContainer: From<C>,
{
let container = pyo3::PyClassInitializer::from(PySliceContainer::from(container))
.create_cell(py)
.expect("Object creation failed.");
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 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() {
Err(NotContiguousError)
} else {
Ok(slice::from_raw_parts(self.data(), self.len()))
}
}
pub unsafe fn as_slice_mut(&self) -> Result<&mut [T], NotContiguousError> {
if !self.is_contiguous() {
Err(NotContiguousError)
} else {
Ok(slice::from_raw_parts_mut(self.data(), self.len()))
}
}
pub fn from_owned_array<'py>(py: Python<'py>, arr: Array<T, D>) -> &'py Self {
let (strides, dims) = (arr.npy_strides(), arr.raw_dim());
let data_ptr = arr.as_ptr();
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, arr) }
}
#[inline(always)]
pub unsafe fn get(&self, index: impl NpyIndex<Dim = D>) -> Option<&T> {
let offset = index.get_checked::<T>(self.shape(), self.strides())?;
Some(&*self.data().offset(offset))
}
#[inline(always)]
pub unsafe fn uget<Idx>(&self, index: Idx) -> &T
where
Idx: NpyIndex<Dim = D>,
{
let offset = index.get_unchecked::<T>(self.strides());
&*self.data().offset(offset)
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn uget_mut<Idx>(&self, index: Idx) -> &mut T
where
Idx: NpyIndex<Dim = D>,
{
let offset = index.get_unchecked::<T>(self.strides());
&mut *(self.data().offset(offset) as *mut _)
}
#[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 to_dyn(&self) -> &PyArray<T, IxDyn> {
let python = self.py();
unsafe { PyArray::from_borrowed_ptr(python, self.as_ptr()) }
}
pub fn get_owned(&self, index: impl NpyIndex<Dim = D>) -> Option<T> {
unsafe { self.get(index) }.cloned()
}
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 unsafe fn as_array(&self) -> ArrayView<'_, T, D> {
let (shape, ptr, inverted_axes) = self.ndarray_shape_ptr();
let mut res = ArrayView::from_shape_ptr(shape, ptr);
inverted_axes.invert(&mut res);
res
}
pub unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D> {
let (shape, ptr, inverted_axes) = self.ndarray_shape_ptr();
let mut res = ArrayViewMut::from_shape_ptr(shape, ptr);
inverted_axes.invert(&mut res);
res
}
pub fn as_raw_array(&self) -> RawArrayView<T, D> {
let (shape, ptr, inverted_axes) = self.ndarray_shape_ptr();
let mut res = unsafe { RawArrayView::from_shape_ptr(shape, ptr) };
inverted_axes.invert(&mut res);
res
}
pub fn as_raw_array_mut(&self) -> RawArrayViewMut<T, D> {
let (shape, ptr, inverted_axes) = self.ndarray_shape_ptr();
let mut res = unsafe { RawArrayViewMut::from_shape_ptr(shape, ptr) };
inverted_axes.invert(&mut res);
res
}
pub fn to_owned_array(&self) -> Array<T, D> {
unsafe { self.as_array() }.to_owned()
}
}
impl<D: Dimension> PyArray<PyObject, D> {
pub fn from_owned_object_array<'py, T>(py: Python<'py>, arr: Array<Py<T>, D>) -> &'py Self {
let (strides, dims) = (arr.npy_strides(), arr.raw_dim());
let data_ptr = arr.as_ptr() as *const PyObject;
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, 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);
if T::IS_COPY {
array.copy_ptr(slice.as_ptr(), slice.len());
} else {
let data_ptr = array.data();
for (i, item) in slice.iter().enumerate() {
data_ptr.add(i).write(item.clone());
}
}
array
}
}
pub fn from_vec<'py>(py: Python<'py>, vec: Vec<T>) -> &'py Self {
IntoPyArray::into_pyarray(vec, py)
}
pub fn from_exact_iter(py: Python<'_>, iter: impl ExactSizeIterator<Item = T>) -> &Self {
unsafe {
let len = iter.len();
let array = Self::new(py, [len], false);
let mut idx = 0;
for item in iter {
assert!(idx < len);
array.uget_raw([idx]).write(item);
idx += 1;
}
assert!(idx == len);
array
}
}
pub fn from_iter(py: Python<'_>, iter: impl IntoIterator<Item = T>) -> &Self {
let iter = iter.into_iter();
let (min_len, max_len) = iter.size_hint();
let mut capacity = max_len.unwrap_or_else(|| min_len.max(512 / mem::size_of::<T>()));
unsafe {
let array = Self::new(py, [capacity], false);
let mut length = 0;
for (i, item) in iter.enumerate() {
length += 1;
if length > capacity {
capacity *= 2;
array
.resize(capacity)
.expect("PyArray::from_iter: Failed to allocate memory");
}
array.uget_raw([i]).write(item);
}
if capacity > length {
array.resize(length).unwrap()
}
array
}
}
pub fn resize(&self, new_elems: usize) -> PyResult<()> {
self.resize_(self.py(), [new_elems], 1, NPY_ORDER::NPY_ANYORDER)
}
#[deprecated(
note = "The wrappers of the array iterator API are deprecated, please use ndarray's `ArrayBase::iter_mut` instead."
)]
#[allow(deprecated)]
pub unsafe fn iter<'py>(&'py self) -> PyResult<NpySingleIter<'py, T, ReadWrite>> {
NpySingleIterBuilder::readwrite(self).build()
}
fn resize_<D: IntoDimension>(
&self,
py: Python,
dims: D,
check_ref: c_int,
order: NPY_ORDER,
) -> PyResult<()> {
let dims = dims.into_dimension();
let mut np_dims = dims.to_npy_dims();
let res = unsafe {
PY_ARRAY_API.PyArray_Resize(
py,
self.as_array_ptr(),
&mut np_dims as *mut npyffi::PyArray_Dims,
check_ref,
order,
)
};
if res.is_null() {
Err(PyErr::fetch(self.py()))
} else {
Ok(())
}
}
}
impl<T: Element> PyArray<T, Ix2> {
pub fn from_vec2<'py>(py: Python<'py>, v: &[Vec<T>]) -> Result<&'py Self, FromVecError> {
let last_len = v.last().map_or(0, |v| v.len());
for v in v {
if v.len() != last_len {
return Err(FromVecError::new(v.len(), last_len));
}
}
let dims = [v.len(), last_len];
unsafe {
let array = Self::new(py, dims, false);
for (y, vy) in v.iter().enumerate() {
for (x, vyx) in vy.iter().enumerate() {
array.uget_raw([y, x]).write(vyx.clone());
}
}
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.last().map_or(0, |v| v.len());
for v in v {
if v.len() != len2 {
return Err(FromVecError::new(v.len(), len2));
}
}
let len3 = v.last().map_or(0, |v| v.last().map_or(0, |v| v.len()));
for v in v {
for v in v {
if v.len() != len3 {
return Err(FromVecError::new(v.len(), len3));
}
}
}
let dims = [v.len(), len2, len3];
unsafe {
let array = Self::new(py, dims, false);
for (z, vz) in v.iter().enumerate() {
for (y, vzy) in vz.iter().enumerate() {
for (x, vzyx) in vzy.iter().enumerate() {
array.uget_raw([z, y, x]).write(vzyx.clone());
}
}
}
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 {
Err(PyErr::fetch(self.py()))
} else {
Ok(())
}
}
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() {
Err(PyErr::fetch(self.py()))
} else {
Ok(unsafe { PyArray::<U, D>::from_owned_ptr(self.py(), ptr) })
}
}
#[inline(always)]
pub fn reshape<'py, ID, D2>(&'py self, dims: ID) -> PyResult<&'py PyArray<T, D2>>
where
ID: IntoDimension<Dim = D2>,
D2: Dimension,
{
self.reshape_with_order(dims, NPY_ORDER::NPY_ANYORDER)
}
pub fn reshape_with_order<'py, ID, D2>(
&'py self,
dims: ID,
order: NPY_ORDER,
) -> PyResult<&'py PyArray<T, D2>>
where
ID: IntoDimension<Dim = D2>,
D2: Dimension,
{
let dims = dims.into_dimension();
let mut np_dims = dims.to_npy_dims();
let ptr = unsafe {
PY_ARRAY_API.PyArray_Newshape(
self.py(),
self.as_array_ptr(),
&mut np_dims as *mut npyffi::PyArray_Dims,
order,
)
};
if ptr.is_null() {
Err(PyErr::fetch(self.py()))
} else {
Ok(unsafe { PyArray::<T, D2>::from_owned_ptr(self.py(), ptr) })
}
}
}
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)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Range;
#[test]
fn test_get_unchecked() {
pyo3::Python::with_gil(|py| {
let array = PyArray::from_slice(py, &[1i32, 2, 3]);
unsafe {
assert_eq!(*array.uget([1]), 2);
}
})
}
#[test]
fn test_dyn_to_owned_array() {
pyo3::Python::with_gil(|py| {
let array = PyArray::from_vec2(py, &[vec![1, 2], vec![3, 4]]).unwrap();
array.to_dyn().to_owned_array();
})
}
#[test]
fn test_hasobject_flag() {
use super::ToPyArray;
use pyo3::{py_run, types::PyList, Py, PyAny};
pyo3::Python::with_gil(|py| {
let a = ndarray::Array2::from_shape_fn((2, 3), |(_i, _j)| PyList::empty(py).into());
let arr: &PyArray<Py<PyAny>, _> = a.to_pyarray(py);
py_run!(py, arr, "assert arr.dtype.hasobject");
});
}
struct InsincereIterator(Range<usize>, usize);
impl Iterator for InsincereIterator {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl ExactSizeIterator for InsincereIterator {
fn len(&self) -> usize {
self.1
}
}
#[test]
#[should_panic]
fn from_exact_iter_too_short() {
Python::with_gil(|py| {
PyArray::from_exact_iter(py, InsincereIterator(0..3, 5));
});
}
#[test]
#[should_panic]
fn from_exact_iter_too_long() {
Python::with_gil(|py| {
PyArray::from_exact_iter(py, InsincereIterator(0..5, 3));
});
}
}