use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API};
use ndarray::*;
use num_traits::AsPrimitive;
use pyo3::{
ffi, prelude::*, type_object, types::PyAny, AsPyPointer, PyDowncastError, PyNativeType,
PyResult,
};
use std::{cell::Cell, mem, os::raw::c_int, ptr, slice};
use std::{iter::ExactSizeIterator, marker::PhantomData};
use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
use crate::error::{FromVecError, NotContiguousError, ShapeError};
use crate::slice_box::SliceBox;
use crate::types::Element;
pub struct PyArray<T, D>(PyAny, PhantomData<T>, PhantomData<D>);
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 {}
pyobject_native_type_convert!(
PyArray<T, D>,
npyffi::PyArrayObject,
*npyffi::PY_ARRAY_API.get_type_object(npyffi::ArrayType::PyArray_Type),
Some("numpy"),
npyffi::PyArray_Check,
T, D
);
pyobject_native_type_named!(PyArray<T, D>, T, D);
impl<'a, T, D> std::convert::From<&'a PyArray<T, D>> for &'a PyAny {
fn from(ob: &'a PyArray<T, D>) -> Self {
unsafe { &*(ob as *const PyArray<T, D> as *const PyAny) }
}
}
impl<'a, T: Element, D: Dimension> FromPyObject<'a> for &'a PyArray<T, D> {
fn extract(ob: &'a PyAny) -> PyResult<Self> {
let array = unsafe {
if npyffi::PyArray_Check(ob.as_ptr()) == 0 {
return Err(PyDowncastError.into());
}
&*(ob as *const PyAny as *const PyArray<T, D>)
};
array.type_check()?;
Ok(array)
}
}
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<T, D> PyArray<T, D> {
pub fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject {
self.as_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) -> crate::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: Python<'_>, ptr: *mut ffi::PyObject) -> &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
}
fn strides_usize(&self) -> &[usize] {
let n = self.ndim();
let ptr = self.as_array_ptr();
unsafe {
let p = (*ptr).strides;
slice::from_raw_parts(p as *const _, n)
}
}
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)
}
}
impl<T: Element, D: Dimension> PyArray<T, D> {
#[inline(always)]
pub fn dims(&self) -> D {
D::from_dimension(&Dim(self.shape())).expect("PyArray::dims different dimension")
}
fn ndarray_shape(&self) -> StrideShape<D> {
let shape: Shape<_> = Dim(self.dims()).into();
let size = mem::size_of::<T>();
let mut st = D::from_dimension(&Dim(self.strides_usize()))
.expect("PyArray::ndarray_shape: dimension mismatching");
st.slice_mut().iter_mut().for_each(|e| *e /= size);
shape.strides(st)
}
pub fn new<ID>(py: Python, dims: ID, is_fortran: bool) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let flags = if is_fortran { 1 } else { 0 };
unsafe { 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_New(
PY_ARRAY_API.get_type_object(npyffi::ArrayType::PyArray_Type),
dims.ndim_cint(),
dims.as_dims_ptr(),
T::ffi_dtype() as i32,
strides as *mut _, ptr::null_mut(), 0, flag, ptr::null_mut(), );
Self::from_owned_ptr(py, ptr)
}
pub(crate) unsafe fn from_boxed_slice<'py, ID>(
py: Python<'py>,
dims: ID,
strides: *const npy_intp,
slice: Box<[T]>,
) -> &'py Self
where
ID: IntoDimension<Dim = D>,
{
let dims = dims.into_dimension();
let container = SliceBox::new(slice);
let data_ptr = container.data;
let cell = pyo3::PyClassInitializer::from(container)
.create_cell(py)
.expect("Object creation failed.");
let ptr = PY_ARRAY_API.PyArray_New(
PY_ARRAY_API.get_type_object(npyffi::ArrayType::PyArray_Type),
dims.ndim_cint(),
dims.as_dims_ptr(),
T::ffi_dtype() as i32,
strides as *mut _, data_ptr as _, mem::size_of::<T>() as i32, 0, ptr::null_mut(), );
PY_ARRAY_API.PyArray_SetBaseObject(ptr as *mut npyffi::PyArrayObject, cell as _);
Self::from_owned_ptr(py, ptr)
}
pub fn zeros<ID>(py: Python, dims: ID, is_fortran: bool) -> &Self
where
ID: IntoDimension<Dim = D>,
{
let dims = dims.into_dimension();
unsafe {
let descr = PY_ARRAY_API.PyArray_DescrFromType(T::ffi_dtype() as i32);
let ptr = PY_ARRAY_API.PyArray_Zeros(
dims.ndim_cint(),
dims.as_dims_ptr(),
descr,
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 fn as_cell_slice(&self) -> Result<&[Cell<T>], NotContiguousError> {
if !self.is_contiguous() {
Err(NotContiguousError)
} else {
Ok(unsafe { slice::from_raw_parts(self.data() as _, 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 {
IntoPyArray::into_pyarray(arr, py)
}
#[inline(always)]
pub fn get<Idx>(&self, index: Idx) -> Option<&T>
where
Idx: NpyIndex<Dim = D>,
{
let offset = index.get_checked::<T>(self.shape(), self.strides())?;
unsafe { 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 _)
}
pub fn to_dyn(&self) -> &PyArray<T, IxDyn> {
let python = self.py();
unsafe { PyArray::from_borrowed_ptr(python, self.as_ptr()) }
}
fn type_check(&self) -> Result<(), ShapeError> {
let truth = unsafe { (*(*self.as_array_ptr()).descr).type_num };
let dim = self.shape().len();
if T::is_same_type(truth) && D::NDIM.map(|n| n == dim).unwrap_or(true) {
Ok(())
} else {
Err(ShapeError::new(truth, dim, T::DATA_TYPE, D::NDIM))
}
}
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> {
ArrayView::from_shape_ptr(self.ndarray_shape(), self.data())
}
pub unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D> {
ArrayViewMut::from_shape_ptr(self.ndarray_shape(), self.data())
}
pub fn to_owned_array(&self) -> Array<T, D> {
unsafe { self.as_array() }.to_owned()
}
}
impl<T: Element> PyArray<T, Ix1> {
pub fn from_slice<'py>(py: Python<'py>, slice: &[T]) -> &'py Self {
let array = PyArray::new(py, [slice.len()], false);
unsafe {
array.copy_ptr(slice.as_ptr(), slice.len());
}
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 {
let array = Self::new(py, [iter.len()], false);
unsafe {
for (i, item) in iter.enumerate() {
*array.uget_mut([i]) = item;
}
}
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>()));
let array = Self::new(py, [capacity], false);
let mut length = 0;
unsafe {
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_mut([i]) = item;
}
}
if capacity > length {
array.resize(length).unwrap()
}
array
}
pub fn resize(&self, new_elems: usize) -> PyResult<()> {
self.resize_([new_elems], 1, NPY_ORDER::NPY_ANYORDER)
}
fn resize_<D: IntoDimension>(
&self,
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(
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());
if v.iter().any(|v| v.len() != last_len) {
return Err(FromVecError::new(v.len(), last_len));
}
let dims = [v.len(), last_len];
let array = Self::new(py, dims, false);
unsafe {
for (y, vy) in v.iter().enumerate() {
for (x, vyx) in vy.iter().enumerate() {
*array.uget_mut([y, x]) = 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());
if v.iter().any(|v| 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()));
if v.iter().any(|v| v.iter().any(|v| v.len() != len3)) {
return Err(FromVecError::new(v.len(), len3));
}
let dims = [v.len(), len2, len3];
let array = Self::new(py, dims, false);
unsafe {
for (z, vz) in v.iter().enumerate() {
for (y, vzy) in vz.iter().enumerate() {
for (x, vzyx) in vzy.iter().enumerate() {
*array.uget_mut([z, y, x]) = 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(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 {
let descr = PY_ARRAY_API.PyArray_DescrFromType(U::ffi_dtype() as i32);
PY_ARRAY_API.PyArray_CastToType(
self.as_array_ptr(),
descr,
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.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(
start.as_(),
stop.as_(),
step.as_(),
T::ffi_dtype() as i32,
);
Self::from_owned_ptr(py, ptr)
}
}
}
#[test]
fn test_get_unchecked() {
let gil = pyo3::Python::acquire_gil();
let array = PyArray::from_slice(gil.python(), &[1i32, 2, 3]);
unsafe {
assert_eq!(*array.uget([1]), 2);
}
}
#[test]
fn test_dyn_to_owned_array() {
let gil = pyo3::Python::acquire_gil();
let array = PyArray::from_vec2(gil.python(), &[vec![1, 2], vec![3, 4]]).unwrap();
array.to_dyn().to_owned_array();
}