Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slim down Element, various dtype-related improvements and changes #256

Merged
merged 21 commits into from
Jan 15, 2022
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
82e641b
Use `PyArray_NewFromDescr`, remove `npy_type()`
aldanor Jan 9, 2022
d1ca21a
Add `PyArrayDescr::into_dtype_ptr()`
aldanor Jan 9, 2022
2067f83
Add `PyArrayDescr::of<T>()`
aldanor Jan 9, 2022
a2ba3fb
Add tests for dtype names (currently failing)
aldanor Jan 9, 2022
e9cc2a6
Rework dtype integer type mapping to match numpy
aldanor Jan 9, 2022
5f1b3b6
Clean up typenums, add `DataType::into_typenum()`
aldanor Jan 9, 2022
612e867
Add `PyArrayDescr::is_equiv_to()`
aldanor Jan 9, 2022
5d55c70
Use `is_equiv_to()` in `FromPyObject::extract()`
aldanor Jan 9, 2022
fdc389e
Remove `Element::is_same_type()`
aldanor Jan 9, 2022
cf25192
`ShapeError` -> `DimensionalityError`/`TypeError`
aldanor Jan 9, 2022
c454a5e
Replace `Element::DATA_TYPE` with `IS_COPY`
aldanor Jan 9, 2022
6688d79
Implement `Element` for `isize`
aldanor Jan 9, 2022
8e7c583
Add a test verifying that `hasobject` flag is set
aldanor Jan 10, 2022
147775d
Rename `c*` aliases to `Complex*`, add docstrings
aldanor Jan 10, 2022
fee3283
Map scalar types directly, bypassing `DataType`
aldanor Jan 10, 2022
2e2b576
Goodbye `DataType` (and update the tests)
aldanor Jan 10, 2022
61ce229
Update the docstring for `Element`
aldanor Jan 10, 2022
f08d6e0
Remove `cfg_if` dependency, clean up dtype macro
aldanor Jan 10, 2022
c3d0a53
Add `dtype()` top-level function for convenience
aldanor Jan 10, 2022
fee612d
(Use `cfg` instead of `size_of::<usize>` in tests)
aldanor Jan 10, 2022
4021797
Update the changelog (descriptors rework)
aldanor Jan 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,17 @@
- Support borrowing arrays that are part of other Python objects via `PyArray::borrow_from_array` ([#230](https://github.com/PyO3/rust-numpy/pull/216))
- `PyArray::new` is now `unsafe`, as it produces uninitialized arrays ([#220](https://github.com/PyO3/rust-numpy/pull/220))
- `rayon` feature is now removed, and directly specifying the feature via `ndarray` dependency is recommended ([#250](https://github.com/PyO3/rust-numpy/pull/250))
- Descriptors rework and related changes ([#256](https://github.com/PyO3/rust-numpy/pull/256)):
- Remove `DataType`
- Add the top-level `dtype` function for easy access to registered dtypes
- Add `PyArrayDescr::of`, `PyArrayDescr::into_dtype_ptr` and `PyArrayDescr::is_equiv_to`
- `Element` trait has been simplified to just `IS_COPY` const and `get_dtype` method
- `Element` is now implemented for `isize`
- `c32` and `c64` aliases have been replaced with `Complex32` and `Complex64`
- `ShapeError` has been split into `TypeError` and `DimensionalityError`
- `i32`, `i64`, `u32` and `u64` are now guaranteed to map to
`np.int32`, `np.int64`, `np.uint32` and `np.uint64` respectively
- Remove `cfg_if` dependency

- v0.15.1
- Make arrays produced via `IntoPyArray`, i.e. those owning Rust data, writeable ([#235](https://github.com/PyO3/rust-numpy/pull/235))
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@ keywords = ["python", "numpy", "ffi", "pyo3"]
license = "BSD-2-Clause"

[dependencies]
cfg-if = "1.0"
libc = "0.2"
num-complex = ">= 0.2, <= 0.4"
num-traits = "0.2"
9 changes: 6 additions & 3 deletions examples/simple-extension/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use numpy::ndarray::{ArrayD, ArrayViewD, ArrayViewMutD};
use numpy::{c64, IntoPyArray, PyArrayDyn, PyReadonlyArrayDyn};
use numpy::{Complex64, IntoPyArray, PyArrayDyn, PyReadonlyArrayDyn};
use pyo3::prelude::{pymodule, PyModule, PyResult, Python};

#[pymodule]
@@ -15,7 +15,7 @@ fn rust_ext(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
}

// complex example
fn conj(x: ArrayViewD<'_, c64>) -> ArrayD<c64> {
fn conj(x: ArrayViewD<'_, Complex64>) -> ArrayD<Complex64> {
x.map(|c| c.conj())
}

@@ -44,7 +44,10 @@ fn rust_ext(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
// wrapper of `conj`
#[pyfn(m)]
#[pyo3(name = "conj")]
fn conj_py<'py>(py: Python<'py>, x: PyReadonlyArrayDyn<'_, c64>) -> &'py PyArrayDyn<c64> {
fn conj_py<'py>(
py: Python<'py>,
x: PyReadonlyArrayDyn<'_, Complex64>,
) -> &'py PyArrayDyn<Complex64> {
conj(x.as_array()).into_pyarray(py)
}

66 changes: 41 additions & 25 deletions src/array.rs
Original file line number Diff line number Diff line change
@@ -15,8 +15,8 @@ use std::{
use std::{iter::ExactSizeIterator, marker::PhantomData};

use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
use crate::dtype::{DataType, Element};
use crate::error::{FromVecError, NotContiguousError, ShapeError};
use crate::dtype::Element;
use crate::error::{DimensionalityError, FromVecError, NotContiguousError, TypeError};
use crate::slice_container::PySliceContainer;

/// A safe, static-typed interface for
@@ -136,13 +136,21 @@ impl<'a, T: Element, D: Dimension> FromPyObject<'a> for &'a PyArray<T, D> {
}
&*(ob as *const PyAny as *const PyArray<T, D>)
};
let dtype = array.dtype();
let dim = array.shape().len();
if T::is_same_type(dtype) && D::NDIM.map(|n| n == dim).unwrap_or(true) {
Ok(array)
} else {
Err(ShapeError::new(dtype, dim, T::DATA_TYPE, D::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());
}

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)
}
}

@@ -160,7 +168,7 @@ impl<T, D> PyArray<T, D> {
/// pyo3::Python::with_gil(|py| {
/// let array = numpy::PyArray::from_vec(py, vec![1, 2, 3i32]);
/// let dtype = array.dtype();
/// assert_eq!(dtype.get_datatype().unwrap(), numpy::DataType::Int32);
/// assert!(dtype.is_equiv_to(numpy::dtype::<i32>(py)));
/// });
/// ```
pub fn dtype(&self) -> &crate::PyArrayDescr {
@@ -428,14 +436,13 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
ID: IntoDimension<Dim = D>,
{
let dims = dims.into_dimension();
let ptr = PY_ARRAY_API.PyArray_New(
let ptr = PY_ARRAY_API.PyArray_NewFromDescr(
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
T::get_dtype(py).into_dtype_ptr(),
dims.ndim_cint(),
dims.as_dims_ptr(),
T::npy_type() as c_int,
strides as *mut npy_intp, // strides
ptr::null_mut(), // data
0, // itemsize
flag, // flag
ptr::null_mut(), // obj
);
@@ -453,16 +460,15 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
ID: IntoDimension<Dim = D>,
{
let dims = dims.into_dimension();
let ptr = PY_ARRAY_API.PyArray_New(
let ptr = PY_ARRAY_API.PyArray_NewFromDescr(
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
T::get_dtype(py).into_dtype_ptr(),
dims.ndim_cint(),
dims.as_dims_ptr(),
T::npy_type() as c_int,
strides as *mut npy_intp, // strides
data_ptr as *mut c_void, // data
mem::size_of::<T>() as c_int, // itemsize
npyffi::NPY_ARRAY_WRITEABLE, // flag
ptr::null_mut(), // obj
strides as *mut npy_intp, // strides
data_ptr as *mut c_void, // data
npyffi::NPY_ARRAY_WRITEABLE, // flag
ptr::null_mut(), // obj
);

PY_ARRAY_API.PyArray_SetBaseObject(
@@ -569,11 +575,10 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
{
let dims = dims.into_dimension();
unsafe {
let dtype = T::get_dtype(py);
let ptr = PY_ARRAY_API.PyArray_Zeros(
dims.ndim_cint(),
dims.as_dims_ptr(),
dtype.into_ptr() as _,
T::get_dtype(py).into_dtype_ptr(),
if is_fortran { -1 } else { 0 },
);
Self::from_owned_ptr(py, ptr)
@@ -847,7 +852,7 @@ 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::DATA_TYPE != DataType::Object {
if T::IS_COPY {
array.copy_ptr(slice.as_ptr(), slice.len());
} else {
let data_ptr = array.data();
@@ -1104,10 +1109,9 @@ impl<T: Element, D> PyArray<T, D> {
/// ```
pub fn cast<'py, U: Element>(&'py self, is_fortran: bool) -> PyResult<&'py PyArray<U, D>> {
let ptr = unsafe {
let dtype = U::get_dtype(self.py());
PY_ARRAY_API.PyArray_CastToType(
self.as_array_ptr(),
dtype.into_ptr() as _,
U::get_dtype(self.py()).into_dtype_ptr(),
if is_fortran { -1 } else { 0 },
)
};
@@ -1193,7 +1197,7 @@ impl<T: Element + AsPrimitive<f64>> PyArray<T, Ix1> {
start.as_(),
stop.as_(),
step.as_(),
T::npy_type() as i32,
T::get_dtype(py).get_typenum(),
);
Self::from_owned_ptr(py, ptr)
}
@@ -1221,4 +1225,16 @@ mod tests {
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");
});
}
}
4 changes: 2 additions & 2 deletions src/convert.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ use std::{mem, os::raw::c_int};

use crate::{
npyffi::{self, npy_intp},
DataType, Element, PyArray,
Element, PyArray,
};

/// Conversion trait from some rust types to `PyArray`.
@@ -123,7 +123,7 @@ where
fn to_pyarray<'py>(&self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
let len = self.len();
match self.order() {
Some(order) if A::DATA_TYPE != DataType::Object => {
Some(order) if A::IS_COPY => {
// if the array is contiguous, copy it by `copy_ptr`.
let strides = self.npy_strides();
unsafe {
330 changes: 165 additions & 165 deletions src/dtype.rs

Large diffs are not rendered by default.

110 changes: 54 additions & 56 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,10 @@
//! Defines error types.
use crate::DataType;
use pyo3::{exceptions as exc, PyErr, PyErrArguments, PyObject, Python, ToPyObject};
use std::fmt;

/// Represents a dimension and dtype of numpy array.
///
/// Only for error formatting.
#[derive(Debug)]
pub(crate) struct ArrayDim {
dim: Option<usize>,
dtype: Option<DataType>,
}
impl fmt::Display for ArrayDim {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ArrayDim { dim, dtype } = self;
match (dim, dtype) {
(Some(dim), Some(dtype)) => write!(f, "dim={:?}, dtype={:?}", dim, dtype),
(None, Some(dtype)) => write!(f, "dim=_, dtype={:?}", dtype),
(Some(dim), None) => write!(f, "dim={:?}, dtype=Unknown", dim),
(None, None) => write!(f, "dim=_, dtype=Unknown"),
}
}
}

/// Represents that shapes of the given arrays don't match.
#[derive(Debug)]
pub struct ShapeError {
from: ArrayDim,
to: ArrayDim,
}
use std::fmt;

impl ShapeError {
pub(crate) fn new(
from_dtype: &crate::PyArrayDescr,
from_dim: usize,
to_type: DataType,
to_dim: Option<usize>,
) -> Self {
ShapeError {
from: ArrayDim {
dim: Some(from_dim),
dtype: from_dtype.get_datatype(),
},
to: ArrayDim {
dim: to_dim,
dtype: Some(to_type),
},
}
}
}
use pyo3::{exceptions as exc, PyErr, PyErrArguments, PyObject, Python, ToPyObject};

impl fmt::Display for ShapeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ShapeError { from, to } = self;
write!(f, "Shape Mismatch:\n from=({}), to=({})", from, to)
}
}
use crate::dtype::PyArrayDescr;

macro_rules! impl_pyerr {
($err_type: ty) => {
@@ -76,7 +24,57 @@ macro_rules! impl_pyerr {
};
}

impl_pyerr!(ShapeError);
/// Represents that dimensionalities of the given arrays don't match.
#[derive(Debug)]
pub struct DimensionalityError {
from: usize,
to: usize,
}

impl DimensionalityError {
pub(crate) fn new(from: usize, to: usize) -> Self {
Self { from, to }
}
}

impl fmt::Display for DimensionalityError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { from, to } = self;
write!(f, "dimensionality mismatch:\n from={}, to={}", from, to)
}
}

impl_pyerr!(DimensionalityError);

/// Represents that types of the given arrays don't match.
#[derive(Debug)]
pub struct TypeError {
from: String,
to: String,
}

impl TypeError {
pub(crate) fn new(from: &PyArrayDescr, to: &PyArrayDescr) -> Self {
let dtype_to_str = |dtype: &PyArrayDescr| {
dtype
.str()
.map_or_else(|_| "(unknown)".into(), |s| s.to_string_lossy().into_owned())
};
Self {
from: dtype_to_str(from),
to: dtype_to_str(to),
}
}
}

impl fmt::Display for TypeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { from, to } = self;
write!(f, "type mismatch:\n from={}, to={}", from, to)
}
}

impl_pyerr!(TypeError);

/// Represents that given vec cannot be treated as array.
#[derive(Debug)]
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -46,8 +46,8 @@ pub use crate::array::{
PyArray6, PyArrayDyn,
};
pub use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
pub use crate::dtype::{c32, c64, DataType, Element, PyArrayDescr};
pub use crate::error::{FromVecError, NotContiguousError, ShapeError};
pub use crate::dtype::{dtype, Complex32, Complex64, Element, PyArrayDescr};
pub use crate::error::{DimensionalityError, FromVecError, NotContiguousError, TypeError};
pub use crate::npyffi::{PY_ARRAY_API, PY_UFUNC_API};
pub use crate::npyiter::{
IterMode, NpyIterFlag, NpyMultiIter, NpyMultiIterBuilder, NpySingleIter, NpySingleIterBuilder,
2 changes: 1 addition & 1 deletion tests/array.rs
Original file line number Diff line number Diff line change
@@ -249,7 +249,7 @@ fn dtype_from_py() {
.downcast()
.unwrap();
assert_eq!(&format!("{:?}", dtype), "dtype('uint32')");
assert_eq!(dtype.get_datatype().unwrap(), numpy::DataType::Uint32);
assert!(dtype.is_equiv_to(numpy::dtype::<u32>(py)));
})
}