From 82e641bdae26b93817f7b9caac4eb65aa14c1f36 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 19:14:54 +0000 Subject: [PATCH 01/21] Use `PyArray_NewFromDescr`, remove `npy_type()` --- src/array.rs | 12 +++++------- src/dtype.rs | 28 +++++++++++++++++----------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/array.rs b/src/array.rs index bb740bc96..ecaf42385 100644 --- a/src/array.rs +++ b/src/array.rs @@ -428,14 +428,13 @@ impl PyArray { ID: IntoDimension, { 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_ptr() as _, 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,14 +452,13 @@ impl PyArray { ID: IntoDimension, { 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_ptr() as _, 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::() as c_int, // itemsize npyffi::NPY_ARRAY_WRITEABLE, // flag ptr::null_mut(), // obj ); @@ -1193,7 +1191,7 @@ impl> PyArray { start.as_(), stop.as_(), step.as_(), - T::npy_type() as i32, + T::get_dtype(py).get_typenum(), ); Self::from_owned_ptr(py, ptr) } diff --git a/src/dtype.rs b/src/dtype.rs index 47f617d1c..5e123a368 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -63,6 +63,11 @@ impl PyArrayDescr { DataType::from_typenum(self.get_typenum()) } + /// Shortcut for creating a descriptor of 'object' type. + pub fn object(py: Python) -> &Self { + Self::from_npy_type(py, NPY_TYPES::NPY_OBJECT) + } + fn from_npy_type(py: Python, npy_type: NPY_TYPES) -> &Self { unsafe { let descr = PY_ARRAY_API.PyArray_DescrFromType(npy_type as i32); @@ -70,7 +75,7 @@ impl PyArrayDescr { } } - fn get_typenum(&self) -> std::os::raw::c_int { + pub(crate) fn get_typenum(&self) -> std::os::raw::c_int { unsafe { *self.as_dtype_ptr() }.type_num } } @@ -206,6 +211,10 @@ impl DataType { /// fn is_same_type(dtype: &PyArrayDescr) -> bool { /// dtype.get_datatype() == Some(DataType::Object) /// } +/// +/// fn get_dtype(py: Python) -> &PyArrayDescr { +/// PyArrayDescr::object(py) +/// } /// } /// /// Python::with_gil(|py| { @@ -223,17 +232,8 @@ pub unsafe trait Element: Clone + Send { /// Returns if the give `dtype` is convertible to `Self` in Rust. fn is_same_type(dtype: &PyArrayDescr) -> bool; - /// Returns the corresponding - /// [Enumerated Type](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). - #[inline] - fn npy_type() -> NPY_TYPES { - Self::DATA_TYPE.into_ctype() - } - /// Create `dtype`. - fn get_dtype(py: Python) -> &PyArrayDescr { - PyArrayDescr::from_npy_type(py, Self::npy_type()) - } + fn get_dtype(py: Python) -> &PyArrayDescr; } macro_rules! impl_num_element { @@ -243,6 +243,9 @@ macro_rules! impl_num_element { fn is_same_type(dtype: &PyArrayDescr) -> bool { $(dtype.get_typenum() == NPY_TYPES::$npy_types as i32 ||)+ false } + fn get_dtype(py: Python) -> &PyArrayDescr { + PyArrayDescr::from_npy_type(py, DataType::$npy_dat_t.into_ctype()) + } } }; } @@ -287,4 +290,7 @@ unsafe impl Element for PyObject { fn is_same_type(dtype: &PyArrayDescr) -> bool { dtype.get_typenum() == NPY_TYPES::NPY_OBJECT as i32 } + fn get_dtype(py: Python) -> &PyArrayDescr { + PyArrayDescr::object(py) + } } From d1ca21a61a98cc173378ebcda231dfe1bbcc25e2 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 19:33:45 +0000 Subject: [PATCH 02/21] Add `PyArrayDescr::into_dtype_ptr()` --- src/array.rs | 10 ++++------ src/dtype.rs | 7 +++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/array.rs b/src/array.rs index ecaf42385..64229607b 100644 --- a/src/array.rs +++ b/src/array.rs @@ -430,7 +430,7 @@ impl PyArray { let dims = dims.into_dimension(); let ptr = PY_ARRAY_API.PyArray_NewFromDescr( PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type), - T::get_dtype(py).into_ptr() as _, + T::get_dtype(py).into_dtype_ptr(), dims.ndim_cint(), dims.as_dims_ptr(), strides as *mut npy_intp, // strides @@ -454,7 +454,7 @@ impl PyArray { let dims = dims.into_dimension(); let ptr = PY_ARRAY_API.PyArray_NewFromDescr( PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type), - T::get_dtype(py).into_ptr() as _, + T::get_dtype(py).into_dtype_ptr(), dims.ndim_cint(), dims.as_dims_ptr(), strides as *mut npy_intp, // strides @@ -567,11 +567,10 @@ impl PyArray { { 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) @@ -1102,10 +1101,9 @@ impl PyArray { /// ``` pub fn cast<'py, U: Element>(&'py self, is_fortran: bool) -> PyResult<&'py PyArray> { 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 }, ) }; diff --git a/src/dtype.rs b/src/dtype.rs index 5e123a368..d64e51fa0 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -43,6 +43,13 @@ impl PyArrayDescr { self.as_ptr() as _ } + /// Returns `self` as `*mut PyArray_Descr` while increasing the reference count. + /// + /// Useful in cases where the descriptor is stolen by the API. + pub fn into_dtype_ptr(&self) -> *mut PyArray_Descr { + self.into_ptr() as _ + } + /// Returns the internal `PyType` that this `dtype` holds. /// /// # Example From 2067f837a4086cef71f0cd15312d537070ddaeb2 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 21:22:22 +0000 Subject: [PATCH 03/21] Add `PyArrayDescr::of()` --- src/dtype.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dtype.rs b/src/dtype.rs index d64e51fa0..eecb5b89c 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -75,6 +75,11 @@ impl PyArrayDescr { Self::from_npy_type(py, NPY_TYPES::NPY_OBJECT) } + /// Returns the type descriptor for a registered type. + pub fn of(py: Python) -> &Self { + T::get_dtype(py) + } + fn from_npy_type(py: Python, npy_type: NPY_TYPES) -> &Self { unsafe { let descr = PY_ARRAY_API.PyArray_DescrFromType(npy_type as i32); From a2ba3fb63708d528b959ca2452393929b1ae87d1 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 21:21:15 +0000 Subject: [PATCH 04/21] Add tests for dtype names (currently failing) --- src/dtype.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/dtype.rs b/src/dtype.rs index eecb5b89c..c38805a7a 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -306,3 +306,39 @@ unsafe impl Element for PyObject { PyArrayDescr::object(py) } } + +#[cfg(test)] +mod tests { + use cfg_if::cfg_if; + + use super::{c32, c64, Element, PyArrayDescr}; + + #[test] + fn test_dtype_names() { + fn type_name(py: pyo3::Python) -> &str { + PyArrayDescr::of::(py).get_type().name().unwrap() + } + pyo3::Python::with_gil(|py| { + assert_eq!(type_name::(py), "bool_"); + assert_eq!(type_name::(py), "int8"); + assert_eq!(type_name::(py), "int16"); + assert_eq!(type_name::(py), "int32"); + assert_eq!(type_name::(py), "int64"); + assert_eq!(type_name::(py), "uint8"); + assert_eq!(type_name::(py), "uint16"); + assert_eq!(type_name::(py), "uint32"); + assert_eq!(type_name::(py), "uint64"); + assert_eq!(type_name::(py), "float32"); + assert_eq!(type_name::(py), "float64"); + assert_eq!(type_name::(py), "complex64"); + assert_eq!(type_name::(py), "complex128"); + cfg_if! { + if #[cfg(target_pointer_width = "64")] { + assert_eq!(type_name::(py), "uint64"); + } else if #[cfg(target_pointer_width = "32")] { + assert_eq!(type_name::(py), "uint32"); + } + } + }) + } +} From e9cc2a61597315d7506c3268488c26cc44fb986b Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 21:23:09 +0000 Subject: [PATCH 05/21] Rework dtype integer type mapping to match numpy --- src/dtype.rs | 153 ++++++++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 68 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index c38805a7a..20f2b40a9 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -1,7 +1,10 @@ +use std::mem::size_of; +use std::os::raw::{c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort}; + use crate::npyffi::{NpyTypes, PyArray_Descr, NPY_TYPES, PY_ARRAY_API}; use cfg_if::cfg_if; +use num_traits::{Bounded, Zero}; use pyo3::{ffi, prelude::*, pyobject_native_type_core, types::PyType, AsPyPointer, PyNativeType}; -use std::os::raw::c_int; pub use num_complex::Complex32 as c32; pub use num_complex::Complex64 as c64; @@ -123,14 +126,14 @@ impl DataType { x if x == NPY_TYPES::NPY_BOOL as i32 => DataType::Bool, x if x == NPY_TYPES::NPY_BYTE as i32 => DataType::Int8, x if x == NPY_TYPES::NPY_SHORT as i32 => DataType::Int16, - x if x == NPY_TYPES::NPY_INT as i32 => DataType::Int32, - x if x == NPY_TYPES::NPY_LONG as i32 => return DataType::from_clong(false), - x if x == NPY_TYPES::NPY_LONGLONG as i32 => DataType::Int64, + x if x == NPY_TYPES::NPY_INT as i32 => Self::integer::()?, + x if x == NPY_TYPES::NPY_LONG as i32 => Self::integer::()?, + x if x == NPY_TYPES::NPY_LONGLONG as i32 => Self::integer::()?, x if x == NPY_TYPES::NPY_UBYTE as i32 => DataType::Uint8, x if x == NPY_TYPES::NPY_USHORT as i32 => DataType::Uint16, - x if x == NPY_TYPES::NPY_UINT as i32 => DataType::Uint32, - x if x == NPY_TYPES::NPY_ULONG as i32 => return DataType::from_clong(true), - x if x == NPY_TYPES::NPY_ULONGLONG as i32 => DataType::Uint64, + x if x == NPY_TYPES::NPY_UINT as i32 => Self::integer::()?, + x if x == NPY_TYPES::NPY_ULONG as i32 => Self::integer::()?, + x if x == NPY_TYPES::NPY_ULONGLONG as i32 => Self::integer::()?, x if x == NPY_TYPES::NPY_FLOAT as i32 => DataType::Float32, x if x == NPY_TYPES::NPY_DOUBLE as i32 => DataType::Float64, x if x == NPY_TYPES::NPY_CFLOAT as i32 => DataType::Complex32, @@ -140,22 +143,64 @@ impl DataType { }) } + #[inline] + fn integer() -> Option { + let is_unsigned = T::min_value() == T::zero(); + let bit_width = size_of::() << 3; + Some(match (is_unsigned, bit_width) { + (false, 8) => Self::Int8, + (false, 16) => Self::Int16, + (false, 32) => Self::Int32, + (false, 64) => Self::Int64, + (true, 8) => Self::Uint8, + (true, 16) => Self::Uint16, + (true, 32) => Self::Uint32, + (true, 64) => Self::Uint64, + _ => return None, + }) + } + /// Convert `self` into /// [Enumerated Types](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). pub fn into_ctype(self) -> NPY_TYPES { + fn npy_int_type_lookup(npy_types: [NPY_TYPES; 3]) -> NPY_TYPES { + // `npy_common.h` defines the integer aliases. In order, it checks: + // NPY_BITSOF_LONG, NPY_BITSOF_LONGLONG, NPY_BITSOF_INT, NPY_BITSOF_SHORT, NPY_BITSOF_CHAR + // and assigns the alias to the first matching size, so we should check in this order. + match size_of::() { + x if x == size_of::() => npy_types[0], + x if x == size_of::() => npy_types[1], + x if x == size_of::() => npy_types[2], + _ => panic!("Unable to match integer type descriptor: {:?}", npy_types), + } + } + match self { DataType::Bool => NPY_TYPES::NPY_BOOL, DataType::Int8 => NPY_TYPES::NPY_BYTE, DataType::Int16 => NPY_TYPES::NPY_SHORT, - DataType::Int32 => NPY_TYPES::NPY_INT, - #[cfg(all(target_pointer_width = "64", not(windows)))] - DataType::Int64 => NPY_TYPES::NPY_LONG, - #[cfg(any(target_pointer_width = "32", windows))] - DataType::Int64 => NPY_TYPES::NPY_LONGLONG, + DataType::Int32 => npy_int_type_lookup::([ + NPY_TYPES::NPY_LONG, + NPY_TYPES::NPY_INT, + NPY_TYPES::NPY_SHORT, + ]), + DataType::Int64 => npy_int_type_lookup::([ + NPY_TYPES::NPY_LONG, + NPY_TYPES::NPY_LONGLONG, + NPY_TYPES::NPY_INT, + ]), DataType::Uint8 => NPY_TYPES::NPY_UBYTE, DataType::Uint16 => NPY_TYPES::NPY_USHORT, - DataType::Uint32 => NPY_TYPES::NPY_UINT, - DataType::Uint64 => NPY_TYPES::NPY_ULONGLONG, + DataType::Uint32 => npy_int_type_lookup::([ + NPY_TYPES::NPY_ULONG, + NPY_TYPES::NPY_UINT, + NPY_TYPES::NPY_USHORT, + ]), + DataType::Uint64 => npy_int_type_lookup::([ + NPY_TYPES::NPY_ULONG, + NPY_TYPES::NPY_ULONGLONG, + NPY_TYPES::NPY_UINT, + ]), DataType::Float32 => NPY_TYPES::NPY_FLOAT, DataType::Float64 => NPY_TYPES::NPY_DOUBLE, DataType::Complex32 => NPY_TYPES::NPY_CFLOAT, @@ -163,25 +208,6 @@ impl DataType { DataType::Object => NPY_TYPES::NPY_OBJECT, } } - - #[inline(always)] - fn from_clong(is_usize: bool) -> Option { - if cfg!(any(target_pointer_width = "32", windows)) { - Some(if is_usize { - DataType::Uint32 - } else { - DataType::Int32 - }) - } else if cfg!(all(target_pointer_width = "64", not(windows))) { - Some(if is_usize { - DataType::Uint64 - } else { - DataType::Int64 - }) - } else { - None - } - } } /// Represents that a type can be an element of `PyArray`. @@ -249,59 +275,50 @@ pub unsafe trait Element: Clone + Send { } macro_rules! impl_num_element { - ($t:ty, $npy_dat_t:ident $(,$npy_types: ident)+) => { - unsafe impl Element for $t { - const DATA_TYPE: DataType = DataType::$npy_dat_t; + ($ty:ty, $data_type:expr) => { + unsafe impl Element for $ty { + const DATA_TYPE: DataType = $data_type; + fn is_same_type(dtype: &PyArrayDescr) -> bool { - $(dtype.get_typenum() == NPY_TYPES::$npy_types as i32 ||)+ false + dtype.get_datatype() == Some($data_type) } + fn get_dtype(py: Python) -> &PyArrayDescr { - PyArrayDescr::from_npy_type(py, DataType::$npy_dat_t.into_ctype()) + PyArrayDescr::from_npy_type(py, $data_type.into_ctype()) } } }; } -impl_num_element!(bool, Bool, NPY_BOOL); -impl_num_element!(i8, Int8, NPY_BYTE); -impl_num_element!(i16, Int16, NPY_SHORT); -impl_num_element!(u8, Uint8, NPY_UBYTE); -impl_num_element!(u16, Uint16, NPY_USHORT); -impl_num_element!(f32, Float32, NPY_FLOAT); -impl_num_element!(f64, Float64, NPY_DOUBLE); -impl_num_element!(c32, Complex32, NPY_CFLOAT); -impl_num_element!(c64, Complex64, NPY_CDOUBLE); +impl_num_element!(bool, DataType::Bool); +impl_num_element!(i8, DataType::Int8); +impl_num_element!(i16, DataType::Int16); +impl_num_element!(i32, DataType::Int32); +impl_num_element!(i64, DataType::Int64); +impl_num_element!(u8, DataType::Uint8); +impl_num_element!(u16, DataType::Uint16); +impl_num_element!(u32, DataType::Uint32); +impl_num_element!(u64, DataType::Uint64); +impl_num_element!(f32, DataType::Float32); +impl_num_element!(f64, DataType::Float64); +impl_num_element!(c32, DataType::Complex32); +impl_num_element!(c64, DataType::Complex64); cfg_if! { - if #[cfg(all(target_pointer_width = "64", windows))] { - impl_num_element!(usize, Uint64, NPY_ULONGLONG); - } else if #[cfg(all(target_pointer_width = "64", not(windows)))] { - impl_num_element!(usize, Uint64, NPY_ULONG, NPY_ULONGLONG); - } else if #[cfg(all(target_pointer_width = "32", windows))] { - impl_num_element!(usize, Uint32, NPY_UINT, NPY_ULONG); - } else if #[cfg(all(target_pointer_width = "32", not(windows)))] { - impl_num_element!(usize, Uint32, NPY_UINT); - } -} -cfg_if! { - if #[cfg(any(target_pointer_width = "32", windows))] { - impl_num_element!(i32, Int32, NPY_INT, NPY_LONG); - impl_num_element!(u32, Uint32, NPY_UINT, NPY_ULONG); - impl_num_element!(i64, Int64, NPY_LONGLONG); - impl_num_element!(u64, Uint64, NPY_ULONGLONG); - } else if #[cfg(all(target_pointer_width = "64", not(windows)))] { - impl_num_element!(i32, Int32, NPY_INT); - impl_num_element!(u32, Uint32, NPY_UINT); - impl_num_element!(i64, Int64, NPY_LONG, NPY_LONGLONG); - impl_num_element!(u64, Uint64, NPY_ULONG, NPY_ULONGLONG); + if #[cfg(target_pointer_width = "64")] { + impl_num_element!(usize, DataType::Uint64); + } else if #[cfg(target_pointer_width = "32")] { + impl_num_element!(usize, DataType::Uint32); } } unsafe impl Element for PyObject { const DATA_TYPE: DataType = DataType::Object; + fn is_same_type(dtype: &PyArrayDescr) -> bool { dtype.get_typenum() == NPY_TYPES::NPY_OBJECT as i32 } + fn get_dtype(py: Python) -> &PyArrayDescr { PyArrayDescr::object(py) } From 5f1b3b68e88e959fccce206dcda6c0edcc05661a Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 21:48:34 +0000 Subject: [PATCH 06/21] Clean up typenums, add `DataType::into_typenum()` --- src/dtype.rs | 60 +++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index 20f2b40a9..f160a3794 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -1,11 +1,12 @@ use std::mem::size_of; use std::os::raw::{c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort}; -use crate::npyffi::{NpyTypes, PyArray_Descr, NPY_TYPES, PY_ARRAY_API}; use cfg_if::cfg_if; use num_traits::{Bounded, Zero}; use pyo3::{ffi, prelude::*, pyobject_native_type_core, types::PyType, AsPyPointer, PyNativeType}; +use crate::npyffi::{NpyTypes, PyArray_Descr, NPY_TYPES, PY_ARRAY_API}; + pub use num_complex::Complex32 as c32; pub use num_complex::Complex64 as c64; @@ -85,17 +86,20 @@ impl PyArrayDescr { fn from_npy_type(py: Python, npy_type: NPY_TYPES) -> &Self { unsafe { - let descr = PY_ARRAY_API.PyArray_DescrFromType(npy_type as i32); + let descr = PY_ARRAY_API.PyArray_DescrFromType(npy_type as _); py.from_owned_ptr(descr as _) } } - pub(crate) fn get_typenum(&self) -> std::os::raw::c_int { + /// Retrieves the + /// [enumerated type](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types) + /// for this type descriptor. + pub fn get_typenum(&self) -> c_int { unsafe { *self.as_dtype_ptr() }.type_num } } -/// Represents numpy data type. +/// Represents NumPy data type. /// /// This is an incomplete counterpart of /// [Enumerated Types](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types) @@ -119,26 +123,32 @@ pub enum DataType { } impl DataType { - /// Construct `DataType` from - /// [Enumerated Types](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). + /// Convert `self` into an + /// [enumerated type](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). + pub fn into_typenum(self) -> c_int { + self.into_npy_type() as _ + } + + /// Construct the data type from an + /// [enumerated type](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). pub fn from_typenum(typenum: c_int) -> Option { Some(match typenum { - x if x == NPY_TYPES::NPY_BOOL as i32 => DataType::Bool, - x if x == NPY_TYPES::NPY_BYTE as i32 => DataType::Int8, - x if x == NPY_TYPES::NPY_SHORT as i32 => DataType::Int16, - x if x == NPY_TYPES::NPY_INT as i32 => Self::integer::()?, - x if x == NPY_TYPES::NPY_LONG as i32 => Self::integer::()?, - x if x == NPY_TYPES::NPY_LONGLONG as i32 => Self::integer::()?, - x if x == NPY_TYPES::NPY_UBYTE as i32 => DataType::Uint8, - x if x == NPY_TYPES::NPY_USHORT as i32 => DataType::Uint16, - x if x == NPY_TYPES::NPY_UINT as i32 => Self::integer::()?, - x if x == NPY_TYPES::NPY_ULONG as i32 => Self::integer::()?, - x if x == NPY_TYPES::NPY_ULONGLONG as i32 => Self::integer::()?, - x if x == NPY_TYPES::NPY_FLOAT as i32 => DataType::Float32, - x if x == NPY_TYPES::NPY_DOUBLE as i32 => DataType::Float64, - x if x == NPY_TYPES::NPY_CFLOAT as i32 => DataType::Complex32, - x if x == NPY_TYPES::NPY_CDOUBLE as i32 => DataType::Complex64, - x if x == NPY_TYPES::NPY_OBJECT as i32 => DataType::Object, + x if x == NPY_TYPES::NPY_BOOL as c_int => DataType::Bool, + x if x == NPY_TYPES::NPY_BYTE as c_int => DataType::Int8, + x if x == NPY_TYPES::NPY_SHORT as c_int => DataType::Int16, + x if x == NPY_TYPES::NPY_INT as c_int => Self::integer::()?, + x if x == NPY_TYPES::NPY_LONG as c_int => Self::integer::()?, + x if x == NPY_TYPES::NPY_LONGLONG as c_int => Self::integer::()?, + x if x == NPY_TYPES::NPY_UBYTE as c_int => DataType::Uint8, + x if x == NPY_TYPES::NPY_USHORT as c_int => DataType::Uint16, + x if x == NPY_TYPES::NPY_UINT as c_int => Self::integer::()?, + x if x == NPY_TYPES::NPY_ULONG as c_int => Self::integer::()?, + x if x == NPY_TYPES::NPY_ULONGLONG as c_int => Self::integer::()?, + x if x == NPY_TYPES::NPY_FLOAT as c_int => DataType::Float32, + x if x == NPY_TYPES::NPY_DOUBLE as c_int => DataType::Float64, + x if x == NPY_TYPES::NPY_CFLOAT as c_int => DataType::Complex32, + x if x == NPY_TYPES::NPY_CDOUBLE as c_int => DataType::Complex64, + x if x == NPY_TYPES::NPY_OBJECT as c_int => DataType::Object, _ => return None, }) } @@ -160,9 +170,7 @@ impl DataType { }) } - /// Convert `self` into - /// [Enumerated Types](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). - pub fn into_ctype(self) -> NPY_TYPES { + fn into_npy_type(self) -> NPY_TYPES { fn npy_int_type_lookup(npy_types: [NPY_TYPES; 3]) -> NPY_TYPES { // `npy_common.h` defines the integer aliases. In order, it checks: // NPY_BITSOF_LONG, NPY_BITSOF_LONGLONG, NPY_BITSOF_INT, NPY_BITSOF_SHORT, NPY_BITSOF_CHAR @@ -284,7 +292,7 @@ macro_rules! impl_num_element { } fn get_dtype(py: Python) -> &PyArrayDescr { - PyArrayDescr::from_npy_type(py, $data_type.into_ctype()) + PyArrayDescr::from_npy_type(py, $data_type.into_npy_type()) } } }; From 612e8671ec4f73539bf89eb21ee1933e5b053e40 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 22:01:51 +0000 Subject: [PATCH 07/21] Add `PyArrayDescr::is_equiv_to()` --- src/dtype.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dtype.rs b/src/dtype.rs index f160a3794..51293501c 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -84,6 +84,11 @@ impl PyArrayDescr { T::get_dtype(py) } + /// Returns true if two type descriptors are equivalent. + pub fn is_equiv_to(&self, other: &Self) -> bool { + unsafe { PY_ARRAY_API.PyArray_EquivTypes(self.as_dtype_ptr(), other.as_dtype_ptr()) != 0 } + } + fn from_npy_type(py: Python, npy_type: NPY_TYPES) -> &Self { unsafe { let descr = PY_ARRAY_API.PyArray_DescrFromType(npy_type as _); From 5d55c70ea9547c0dd7d1fa44c54731beee84deaa Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 22:02:49 +0000 Subject: [PATCH 08/21] Use `is_equiv_to()` in `FromPyObject::extract()` --- src/array.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/array.rs b/src/array.rs index 64229607b..f9968ce53 100644 --- a/src/array.rs +++ b/src/array.rs @@ -136,12 +136,13 @@ impl<'a, T: Element, D: Dimension> FromPyObject<'a> for &'a PyArray { } &*(ob as *const PyAny as *const PyArray) }; - let dtype = array.dtype(); + let src_dtype = array.dtype(); + let dst_dtype = T::get_dtype(ob.py()); let dim = array.shape().len(); - if T::is_same_type(dtype) && D::NDIM.map(|n| n == dim).unwrap_or(true) { + if src_dtype.is_equiv_to(dst_dtype) && D::NDIM.map(|n| n == dim).unwrap_or(true) { Ok(array) } else { - Err(ShapeError::new(dtype, dim, T::DATA_TYPE, D::NDIM).into()) + Err(ShapeError::new(src_dtype, dim, T::DATA_TYPE, D::NDIM).into()) } } } @@ -457,10 +458,10 @@ impl PyArray { T::get_dtype(py).into_dtype_ptr(), dims.ndim_cint(), dims.as_dims_ptr(), - strides as *mut npy_intp, // strides - data_ptr as *mut c_void, // data - 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( From fdc389e8165b0636c3a30735bd7a49669e047662 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 22:03:46 +0000 Subject: [PATCH 09/21] Remove `Element::is_same_type()` --- src/dtype.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index 51293501c..61d444bd7 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -259,10 +259,6 @@ impl DataType { /// unsafe impl Element for Wrapper { /// const DATA_TYPE: DataType = DataType::Object; /// -/// fn is_same_type(dtype: &PyArrayDescr) -> bool { -/// dtype.get_datatype() == Some(DataType::Object) -/// } -/// /// fn get_dtype(py: Python) -> &PyArrayDescr { /// PyArrayDescr::object(py) /// } @@ -280,9 +276,6 @@ pub unsafe trait Element: Clone + Send { /// `DataType` corresponding to this type. const DATA_TYPE: DataType; - /// Returns if the give `dtype` is convertible to `Self` in Rust. - fn is_same_type(dtype: &PyArrayDescr) -> bool; - /// Create `dtype`. fn get_dtype(py: Python) -> &PyArrayDescr; } @@ -292,10 +285,6 @@ macro_rules! impl_num_element { unsafe impl Element for $ty { const DATA_TYPE: DataType = $data_type; - fn is_same_type(dtype: &PyArrayDescr) -> bool { - dtype.get_datatype() == Some($data_type) - } - fn get_dtype(py: Python) -> &PyArrayDescr { PyArrayDescr::from_npy_type(py, $data_type.into_npy_type()) } @@ -328,10 +317,6 @@ cfg_if! { unsafe impl Element for PyObject { const DATA_TYPE: DataType = DataType::Object; - fn is_same_type(dtype: &PyArrayDescr) -> bool { - dtype.get_typenum() == NPY_TYPES::NPY_OBJECT as i32 - } - fn get_dtype(py: Python) -> &PyArrayDescr { PyArrayDescr::object(py) } From cf25192c170523cd50269e7ac89ebdfac360cd43 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 22:28:58 +0000 Subject: [PATCH 10/21] `ShapeError` -> `DimensionalityError`/`TypeError` --- src/array.rs | 19 ++++++--- src/error.rs | 110 +++++++++++++++++++++++++-------------------------- src/lib.rs | 2 +- 3 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/array.rs b/src/array.rs index f9968ce53..75ac8c8c5 100644 --- a/src/array.rs +++ b/src/array.rs @@ -16,7 +16,7 @@ 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::error::{DimensionalityError, FromVecError, NotContiguousError, TypeError}; use crate::slice_container::PySliceContainer; /// A safe, static-typed interface for @@ -136,14 +136,21 @@ impl<'a, T: Element, D: Dimension> FromPyObject<'a> for &'a PyArray { } &*(ob as *const PyAny as *const PyArray) }; + let src_dtype = array.dtype(); let dst_dtype = T::get_dtype(ob.py()); - let dim = array.shape().len(); - if src_dtype.is_equiv_to(dst_dtype) && D::NDIM.map(|n| n == dim).unwrap_or(true) { - Ok(array) - } else { - Err(ShapeError::new(src_dtype, dim, T::DATA_TYPE, D::NDIM).into()) + 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) } } diff --git a/src/error.rs b/src/error.rs index 2eaca331d..c69df25b4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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, - dtype: Option, -} -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, - ) -> 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)] diff --git a/src/lib.rs b/src/lib.rs index 3b7629415..baec106de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ pub use crate::array::{ }; 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::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, From c454a5eae5e6358aad2a21a676e04338d01e6217 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 22:34:21 +0000 Subject: [PATCH 11/21] Replace `Element::DATA_TYPE` with `IS_COPY` --- src/array.rs | 4 ++-- src/convert.rs | 4 ++-- src/dtype.rs | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/array.rs b/src/array.rs index 75ac8c8c5..cc67f20ed 100644 --- a/src/array.rs +++ b/src/array.rs @@ -15,7 +15,7 @@ use std::{ use std::{iter::ExactSizeIterator, marker::PhantomData}; use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; -use crate::dtype::{DataType, Element}; +use crate::dtype::Element; use crate::error::{DimensionalityError, FromVecError, NotContiguousError, TypeError}; use crate::slice_container::PySliceContainer; @@ -852,7 +852,7 @@ impl PyArray { 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(); diff --git a/src/convert.rs b/src/convert.rs index 5ea649458..b7e585570 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -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 { 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 { diff --git a/src/dtype.rs b/src/dtype.rs index 61d444bd7..9ac5eb961 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -257,7 +257,7 @@ impl DataType { /// pub struct Wrapper(pub Py); /// /// unsafe impl Element for Wrapper { -/// const DATA_TYPE: DataType = DataType::Object; +/// const IS_COPY: bool = false; /// /// fn get_dtype(py: Python) -> &PyArrayDescr { /// PyArrayDescr::object(py) @@ -273,17 +273,23 @@ impl DataType { /// }); /// ``` pub unsafe trait Element: Clone + Send { - /// `DataType` corresponding to this type. - const DATA_TYPE: DataType; + /// Flag that indicates whether this type is trivially copyable. + /// + /// It should be set to true for all trivially copyable types (like scalar types + /// and record/array types only containing trivially copyable fields and elements). + /// + /// This flag should *always* be set to `false` for object types or record types + /// that contain object-type fields. + const IS_COPY: bool; - /// Create `dtype`. + /// Returns the associated array descriptor ("dtype") for the given type. fn get_dtype(py: Python) -> &PyArrayDescr; } macro_rules! impl_num_element { ($ty:ty, $data_type:expr) => { unsafe impl Element for $ty { - const DATA_TYPE: DataType = $data_type; + const IS_COPY: bool = true; fn get_dtype(py: Python) -> &PyArrayDescr { PyArrayDescr::from_npy_type(py, $data_type.into_npy_type()) @@ -315,7 +321,7 @@ cfg_if! { } unsafe impl Element for PyObject { - const DATA_TYPE: DataType = DataType::Object; + const IS_COPY: bool = false; fn get_dtype(py: Python) -> &PyArrayDescr { PyArrayDescr::object(py) From 6688d79f0642655d19da4334365e26a5240bf42e Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 9 Jan 2022 22:49:04 +0000 Subject: [PATCH 12/21] Implement `Element` for `isize` --- src/dtype.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index 9ac5eb961..4ff97f8e8 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -313,10 +313,9 @@ impl_num_element!(c32, DataType::Complex32); impl_num_element!(c64, DataType::Complex64); cfg_if! { - if #[cfg(target_pointer_width = "64")] { - impl_num_element!(usize, DataType::Uint64); - } else if #[cfg(target_pointer_width = "32")] { - impl_num_element!(usize, DataType::Uint32); + if #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] { + impl_num_element!(usize, DataType::integer::().unwrap()); + impl_num_element!(isize, DataType::integer::().unwrap()); } } @@ -330,7 +329,7 @@ unsafe impl Element for PyObject { #[cfg(test)] mod tests { - use cfg_if::cfg_if; + use std::mem::size_of; use super::{c32, c64, Element, PyArrayDescr}; @@ -353,13 +352,17 @@ mod tests { assert_eq!(type_name::(py), "float64"); assert_eq!(type_name::(py), "complex64"); assert_eq!(type_name::(py), "complex128"); - cfg_if! { - if #[cfg(target_pointer_width = "64")] { - assert_eq!(type_name::(py), "uint64"); - } else if #[cfg(target_pointer_width = "32")] { + match size_of::() { + 32 => { assert_eq!(type_name::(py), "uint32"); + assert_eq!(type_name::(py), "int32"); + } + 64 => { + assert_eq!(type_name::(py), "uint64"); + assert_eq!(type_name::(py), "int64"); } + _ => {} } - }) + }); } } From 8e7c58322ef781431b95df88806a602b49646541 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 08:53:26 +0000 Subject: [PATCH 13/21] Add a test verifying that `hasobject` flag is set --- src/array.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/array.rs b/src/array.rs index cc67f20ed..7b1e07e0b 100644 --- a/src/array.rs +++ b/src/array.rs @@ -1225,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, _> = a.to_pyarray(py); + py_run!(py, arr, "assert arr.dtype.hasobject"); + }); + } } From 147775d72150d10aa5bf9cd54e7d1439aeb99881 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 09:59:57 +0000 Subject: [PATCH 14/21] Rename `c*` aliases to `Complex*`, add docstrings --- examples/simple-extension/src/lib.rs | 9 ++++++--- src/dtype.rs | 18 ++++++++++-------- src/lib.rs | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/examples/simple-extension/src/lib.rs b/examples/simple-extension/src/lib.rs index 321377136..9bfa847e9 100644 --- a/examples/simple-extension/src/lib.rs +++ b/examples/simple-extension/src/lib.rs @@ -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 { + fn conj(x: ArrayViewD<'_, Complex64>) -> ArrayD { 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 { + fn conj_py<'py>( + py: Python<'py>, + x: PyReadonlyArrayDyn<'_, Complex64>, + ) -> &'py PyArrayDyn { conj(x.as_array()).into_pyarray(py) } diff --git a/src/dtype.rs b/src/dtype.rs index 4ff97f8e8..84ce8a9a8 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -7,8 +7,7 @@ use pyo3::{ffi, prelude::*, pyobject_native_type_core, types::PyType, AsPyPointe use crate::npyffi::{NpyTypes, PyArray_Descr, NPY_TYPES, PY_ARRAY_API}; -pub use num_complex::Complex32 as c32; -pub use num_complex::Complex64 as c64; +pub use num_complex::{Complex32, Complex64}; /// Binding of [`numpy.dtype`](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html). /// @@ -287,7 +286,8 @@ pub unsafe trait Element: Clone + Send { } macro_rules! impl_num_element { - ($ty:ty, $data_type:expr) => { + ($ty:ty, $data_type:expr $(,#[$meta:meta])*) => { + $(#[$meta])* unsafe impl Element for $ty { const IS_COPY: bool = true; @@ -309,8 +309,10 @@ impl_num_element!(u32, DataType::Uint32); impl_num_element!(u64, DataType::Uint64); impl_num_element!(f32, DataType::Float32); impl_num_element!(f64, DataType::Float64); -impl_num_element!(c32, DataType::Complex32); -impl_num_element!(c64, DataType::Complex64); +impl_num_element!(Complex32, DataType::Complex32, + #[doc = "Complex type with `f32` components which maps to `np.csingle` (`np.complex64`)."]); +impl_num_element!(Complex64, DataType::Complex64, + #[doc = "Complex type with `f64` components which maps to `np.cdouble` (`np.complex128`)."]); cfg_if! { if #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] { @@ -331,7 +333,7 @@ unsafe impl Element for PyObject { mod tests { use std::mem::size_of; - use super::{c32, c64, Element, PyArrayDescr}; + use super::{Complex32, Complex64, Element, PyArrayDescr}; #[test] fn test_dtype_names() { @@ -350,8 +352,8 @@ mod tests { assert_eq!(type_name::(py), "uint64"); assert_eq!(type_name::(py), "float32"); assert_eq!(type_name::(py), "float64"); - assert_eq!(type_name::(py), "complex64"); - assert_eq!(type_name::(py), "complex128"); + assert_eq!(type_name::(py), "complex64"); + assert_eq!(type_name::(py), "complex128"); match size_of::() { 32 => { assert_eq!(type_name::(py), "uint32"); diff --git a/src/lib.rs b/src/lib.rs index baec106de..52bace0a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ 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::dtype::{Complex32, Complex64, DataType, Element, PyArrayDescr}; pub use crate::error::{DimensionalityError, FromVecError, NotContiguousError, TypeError}; pub use crate::npyffi::{PY_ARRAY_API, PY_UFUNC_API}; pub use crate::npyiter::{ From fee3283d99391b364d0ce823f8d8f6fd7fff12ce Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 10:29:40 +0000 Subject: [PATCH 15/21] Map scalar types directly, bypassing `DataType` --- src/dtype.rs | 88 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index 84ce8a9a8..d35608d8c 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -285,39 +285,89 @@ pub unsafe trait Element: Clone + Send { fn get_dtype(py: Python) -> &PyArrayDescr; } -macro_rules! impl_num_element { - ($ty:ty, $data_type:expr $(,#[$meta:meta])*) => { +fn npy_int_type_lookup(npy_types: [NPY_TYPES; 3]) -> NPY_TYPES { + // `npy_common.h` defines the integer aliases. In order, it checks: + // NPY_BITSOF_LONG, NPY_BITSOF_LONGLONG, NPY_BITSOF_INT, NPY_BITSOF_SHORT, NPY_BITSOF_CHAR + // and assigns the alias to the first matching size, so we should check in this order. + match size_of::() { + x if x == size_of::() => npy_types[0], + x if x == size_of::() => npy_types[1], + x if x == size_of::() => npy_types[2], + _ => panic!("Unable to match integer type descriptor: {:?}", npy_types), + } +} + +fn npy_int_type() -> NPY_TYPES { + let is_unsigned = T::min_value() == T::zero(); + let bit_width = size_of::() << 3; + + match (is_unsigned, bit_width) { + (false, 8) => NPY_TYPES::NPY_BYTE, + (false, 16) => NPY_TYPES::NPY_SHORT, + (false, 32) => npy_int_type_lookup::([ + NPY_TYPES::NPY_LONG, + NPY_TYPES::NPY_INT, + NPY_TYPES::NPY_SHORT, + ]), + (false, 64) => npy_int_type_lookup::([ + NPY_TYPES::NPY_LONG, + NPY_TYPES::NPY_LONGLONG, + NPY_TYPES::NPY_INT, + ]), + (true, 8) => NPY_TYPES::NPY_UBYTE, + (true, 16) => NPY_TYPES::NPY_USHORT, + (true, 32) => npy_int_type_lookup::([ + NPY_TYPES::NPY_ULONG, + NPY_TYPES::NPY_UINT, + NPY_TYPES::NPY_USHORT, + ]), + (true, 64) => npy_int_type_lookup::([ + NPY_TYPES::NPY_ULONG, + NPY_TYPES::NPY_ULONGLONG, + NPY_TYPES::NPY_UINT, + ]), + _ => unreachable!(), + } +} + +macro_rules! impl_element_scalar { + (@impl: $ty:ty, $npy_type:expr $(,#[$meta:meta])*) => { $(#[$meta])* unsafe impl Element for $ty { const IS_COPY: bool = true; - fn get_dtype(py: Python) -> &PyArrayDescr { - PyArrayDescr::from_npy_type(py, $data_type.into_npy_type()) + PyArrayDescr::from_npy_type(py, $npy_type) } } }; + ($ty:ty, $npy_type:ident $(,#[$meta:meta])*) => { + impl_element_scalar!(@impl: $ty, NPY_TYPES::$npy_type $(,#[$meta])*); + }; + ($ty:ty $(,#[$meta:meta])*) => { + impl_element_scalar!(@impl: $ty, npy_int_type::<$ty>() $(,#[$meta])*); + }; } -impl_num_element!(bool, DataType::Bool); -impl_num_element!(i8, DataType::Int8); -impl_num_element!(i16, DataType::Int16); -impl_num_element!(i32, DataType::Int32); -impl_num_element!(i64, DataType::Int64); -impl_num_element!(u8, DataType::Uint8); -impl_num_element!(u16, DataType::Uint16); -impl_num_element!(u32, DataType::Uint32); -impl_num_element!(u64, DataType::Uint64); -impl_num_element!(f32, DataType::Float32); -impl_num_element!(f64, DataType::Float64); -impl_num_element!(Complex32, DataType::Complex32, +impl_element_scalar!(bool, NPY_BOOL); +impl_element_scalar!(i8); +impl_element_scalar!(i16); +impl_element_scalar!(i32); +impl_element_scalar!(i64); +impl_element_scalar!(u8); +impl_element_scalar!(u16); +impl_element_scalar!(u32); +impl_element_scalar!(u64); +impl_element_scalar!(f32, NPY_FLOAT); +impl_element_scalar!(f64, NPY_DOUBLE); +impl_element_scalar!(Complex32, NPY_CFLOAT, #[doc = "Complex type with `f32` components which maps to `np.csingle` (`np.complex64`)."]); -impl_num_element!(Complex64, DataType::Complex64, +impl_element_scalar!(Complex64, NPY_CDOUBLE, #[doc = "Complex type with `f64` components which maps to `np.cdouble` (`np.complex128`)."]); cfg_if! { if #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] { - impl_num_element!(usize, DataType::integer::().unwrap()); - impl_num_element!(isize, DataType::integer::().unwrap()); + impl_element_scalar!(usize); + impl_element_scalar!(isize); } } From 2e2b5763997708f534996876bf9a1e4932f09e60 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 12:18:36 +0000 Subject: [PATCH 16/21] Goodbye `DataType` (and update the tests) --- src/array.rs | 2 +- src/dtype.rs | 128 +------------------------------------------------ src/lib.rs | 2 +- tests/array.rs | 2 +- 4 files changed, 5 insertions(+), 129 deletions(-) diff --git a/src/array.rs b/src/array.rs index 7b1e07e0b..de6e20e01 100644 --- a/src/array.rs +++ b/src/array.rs @@ -168,7 +168,7 @@ impl PyArray { /// 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::PyArrayDescr::of::(py))); /// }); /// ``` pub fn dtype(&self) -> &crate::PyArrayDescr { diff --git a/src/dtype.rs b/src/dtype.rs index d35608d8c..f4d501a08 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -21,7 +21,7 @@ pub use num_complex::{Complex32, Complex64}; /// .unwrap() /// .downcast() /// .unwrap(); -/// assert_eq!(dtype.get_datatype().unwrap(), numpy::DataType::Float64); +/// assert!(dtype.is_equiv_to(numpy::PyArrayDescr::of::(py))); /// }); /// ``` pub struct PyArrayDescr(PyAny); @@ -68,11 +68,6 @@ impl PyArrayDescr { unsafe { PyType::from_type_ptr(self.py(), dtype_type_ptr) } } - /// Returns the data type as `DataType` enum. - pub fn get_datatype(&self) -> Option { - DataType::from_typenum(self.get_typenum()) - } - /// Shortcut for creating a descriptor of 'object' type. pub fn object(py: Python) -> &Self { Self::from_npy_type(py, NPY_TYPES::NPY_OBJECT) @@ -103,125 +98,6 @@ impl PyArrayDescr { } } -/// Represents NumPy data type. -/// -/// This is an incomplete counterpart of -/// [Enumerated Types](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types) -/// in numpy C-API. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum DataType { - Bool, - Int8, - Int16, - Int32, - Int64, - Uint8, - Uint16, - Uint32, - Uint64, - Float32, - Float64, - Complex32, - Complex64, - Object, -} - -impl DataType { - /// Convert `self` into an - /// [enumerated type](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). - pub fn into_typenum(self) -> c_int { - self.into_npy_type() as _ - } - - /// Construct the data type from an - /// [enumerated type](https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types). - pub fn from_typenum(typenum: c_int) -> Option { - Some(match typenum { - x if x == NPY_TYPES::NPY_BOOL as c_int => DataType::Bool, - x if x == NPY_TYPES::NPY_BYTE as c_int => DataType::Int8, - x if x == NPY_TYPES::NPY_SHORT as c_int => DataType::Int16, - x if x == NPY_TYPES::NPY_INT as c_int => Self::integer::()?, - x if x == NPY_TYPES::NPY_LONG as c_int => Self::integer::()?, - x if x == NPY_TYPES::NPY_LONGLONG as c_int => Self::integer::()?, - x if x == NPY_TYPES::NPY_UBYTE as c_int => DataType::Uint8, - x if x == NPY_TYPES::NPY_USHORT as c_int => DataType::Uint16, - x if x == NPY_TYPES::NPY_UINT as c_int => Self::integer::()?, - x if x == NPY_TYPES::NPY_ULONG as c_int => Self::integer::()?, - x if x == NPY_TYPES::NPY_ULONGLONG as c_int => Self::integer::()?, - x if x == NPY_TYPES::NPY_FLOAT as c_int => DataType::Float32, - x if x == NPY_TYPES::NPY_DOUBLE as c_int => DataType::Float64, - x if x == NPY_TYPES::NPY_CFLOAT as c_int => DataType::Complex32, - x if x == NPY_TYPES::NPY_CDOUBLE as c_int => DataType::Complex64, - x if x == NPY_TYPES::NPY_OBJECT as c_int => DataType::Object, - _ => return None, - }) - } - - #[inline] - fn integer() -> Option { - let is_unsigned = T::min_value() == T::zero(); - let bit_width = size_of::() << 3; - Some(match (is_unsigned, bit_width) { - (false, 8) => Self::Int8, - (false, 16) => Self::Int16, - (false, 32) => Self::Int32, - (false, 64) => Self::Int64, - (true, 8) => Self::Uint8, - (true, 16) => Self::Uint16, - (true, 32) => Self::Uint32, - (true, 64) => Self::Uint64, - _ => return None, - }) - } - - fn into_npy_type(self) -> NPY_TYPES { - fn npy_int_type_lookup(npy_types: [NPY_TYPES; 3]) -> NPY_TYPES { - // `npy_common.h` defines the integer aliases. In order, it checks: - // NPY_BITSOF_LONG, NPY_BITSOF_LONGLONG, NPY_BITSOF_INT, NPY_BITSOF_SHORT, NPY_BITSOF_CHAR - // and assigns the alias to the first matching size, so we should check in this order. - match size_of::() { - x if x == size_of::() => npy_types[0], - x if x == size_of::() => npy_types[1], - x if x == size_of::() => npy_types[2], - _ => panic!("Unable to match integer type descriptor: {:?}", npy_types), - } - } - - match self { - DataType::Bool => NPY_TYPES::NPY_BOOL, - DataType::Int8 => NPY_TYPES::NPY_BYTE, - DataType::Int16 => NPY_TYPES::NPY_SHORT, - DataType::Int32 => npy_int_type_lookup::([ - NPY_TYPES::NPY_LONG, - NPY_TYPES::NPY_INT, - NPY_TYPES::NPY_SHORT, - ]), - DataType::Int64 => npy_int_type_lookup::([ - NPY_TYPES::NPY_LONG, - NPY_TYPES::NPY_LONGLONG, - NPY_TYPES::NPY_INT, - ]), - DataType::Uint8 => NPY_TYPES::NPY_UBYTE, - DataType::Uint16 => NPY_TYPES::NPY_USHORT, - DataType::Uint32 => npy_int_type_lookup::([ - NPY_TYPES::NPY_ULONG, - NPY_TYPES::NPY_UINT, - NPY_TYPES::NPY_USHORT, - ]), - DataType::Uint64 => npy_int_type_lookup::([ - NPY_TYPES::NPY_ULONG, - NPY_TYPES::NPY_ULONGLONG, - NPY_TYPES::NPY_UINT, - ]), - DataType::Float32 => NPY_TYPES::NPY_FLOAT, - DataType::Float64 => NPY_TYPES::NPY_DOUBLE, - DataType::Complex32 => NPY_TYPES::NPY_CFLOAT, - DataType::Complex64 => NPY_TYPES::NPY_CDOUBLE, - DataType::Object => NPY_TYPES::NPY_OBJECT, - } - } -} - /// Represents that a type can be an element of `PyArray`. /// /// Currently, only integer/float/complex types are supported. @@ -243,7 +119,7 @@ impl DataType { /// on Python's heap using PyO3's [Py](pyo3::Py) type. /// /// ``` -/// use numpy::{ndarray::Array2, DataType, Element, PyArray, PyArrayDescr, ToPyArray}; +/// use numpy::{ndarray::Array2, Element, PyArray, PyArrayDescr, ToPyArray}; /// use pyo3::{pyclass, Py, Python}; /// /// #[pyclass] diff --git a/src/lib.rs b/src/lib.rs index 52bace0a9..227e126bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ pub use crate::array::{ PyArray6, PyArrayDyn, }; pub use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; -pub use crate::dtype::{Complex32, Complex64, DataType, Element, PyArrayDescr}; +pub use crate::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::{ diff --git a/tests/array.rs b/tests/array.rs index c6fd1f831..95db816df 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -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::PyArrayDescr::of::(py))); }) } From 61ce229e3d0d81c1bb52c077fd4eaafd2e428842 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 12:18:45 +0000 Subject: [PATCH 17/21] Update the docstring for `Element` --- src/dtype.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index f4d501a08..d9068746b 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -107,11 +107,14 @@ impl PyArrayDescr { /// /// # Safety /// -/// A type `T` that implements this trait should be safe when managed in numpy array, -/// thus implementing this trait is marked unsafe. -/// This means that all data types except for `DataType::Object` are assumed to be trivially copyable. -/// Furthermore, it is assumed that for `DataType::Object` the elements are pointers into the Python heap -/// and that the corresponding `Clone` implemenation will never panic as it only increases the reference count. +/// A type `T` that implements this trait should be safe when managed in numpy +/// array, thus implementing this trait is marked unsafe. Data types that don't +/// contain Python objects (i.e., either the object type itself or record types +/// containing object-type fields) are assumed to be trivially copyable, which +/// is reflected in the `IS_COPY` flag. Furthermore, it is assumed that for +/// the object type the elements are pointers into the Python heap and that the +/// corresponding `Clone` implemenation will never panic as it only increases +/// the reference count. /// /// # Custom element types /// From f08d6e094391fad3fa8dae304a0d057997fa4e0c Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 12:26:03 +0000 Subject: [PATCH 18/21] Remove `cfg_if` dependency, clean up dtype macro --- Cargo.toml | 1 - src/dtype.rs | 35 ++++++++++++----------------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8981aba5c..6bcb761e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/dtype.rs b/src/dtype.rs index d9068746b..dc08ba84e 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -1,7 +1,6 @@ use std::mem::size_of; use std::os::raw::{c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort}; -use cfg_if::cfg_if; use num_traits::{Bounded, Zero}; use pyo3::{ffi, prelude::*, pyobject_native_type_core, types::PyType, AsPyPointer, PyNativeType}; @@ -219,36 +218,26 @@ macro_rules! impl_element_scalar { } } }; - ($ty:ty, $npy_type:ident $(,#[$meta:meta])*) => { + ($ty:ty => $npy_type:ident $(,#[$meta:meta])*) => { impl_element_scalar!(@impl: $ty, NPY_TYPES::$npy_type $(,#[$meta])*); }; - ($ty:ty $(,#[$meta:meta])*) => { - impl_element_scalar!(@impl: $ty, npy_int_type::<$ty>() $(,#[$meta])*); + ($($tys:ty),+) => { + $(impl_element_scalar!(@impl: $tys, npy_int_type::<$tys>());)+ }; } -impl_element_scalar!(bool, NPY_BOOL); -impl_element_scalar!(i8); -impl_element_scalar!(i16); -impl_element_scalar!(i32); -impl_element_scalar!(i64); -impl_element_scalar!(u8); -impl_element_scalar!(u16); -impl_element_scalar!(u32); -impl_element_scalar!(u64); -impl_element_scalar!(f32, NPY_FLOAT); -impl_element_scalar!(f64, NPY_DOUBLE); -impl_element_scalar!(Complex32, NPY_CFLOAT, +impl_element_scalar!(bool => NPY_BOOL); +impl_element_scalar!(i8, i16, i32, i64); +impl_element_scalar!(u8, u16, u32, u64); +impl_element_scalar!(f32 => NPY_FLOAT); +impl_element_scalar!(f64 => NPY_DOUBLE); +impl_element_scalar!(Complex32 => NPY_CFLOAT, #[doc = "Complex type with `f32` components which maps to `np.csingle` (`np.complex64`)."]); -impl_element_scalar!(Complex64, NPY_CDOUBLE, +impl_element_scalar!(Complex64 => NPY_CDOUBLE, #[doc = "Complex type with `f64` components which maps to `np.cdouble` (`np.complex128`)."]); -cfg_if! { - if #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] { - impl_element_scalar!(usize); - impl_element_scalar!(isize); - } -} +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl_element_scalar!(usize, isize); unsafe impl Element for PyObject { const IS_COPY: bool = false; From c3d0a538885b7ab415c9e9eba6c8e3b0261b2fde Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 12:32:03 +0000 Subject: [PATCH 19/21] Add `dtype()` top-level function for convenience --- src/array.rs | 2 +- src/dtype.rs | 13 +++++++++---- src/lib.rs | 2 +- tests/array.rs | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/array.rs b/src/array.rs index de6e20e01..3463b0c3d 100644 --- a/src/array.rs +++ b/src/array.rs @@ -168,7 +168,7 @@ impl PyArray { /// pyo3::Python::with_gil(|py| { /// let array = numpy::PyArray::from_vec(py, vec![1, 2, 3i32]); /// let dtype = array.dtype(); - /// assert!(dtype.is_equiv_to(numpy::PyArrayDescr::of::(py))); + /// assert!(dtype.is_equiv_to(numpy::dtype::(py))); /// }); /// ``` pub fn dtype(&self) -> &crate::PyArrayDescr { diff --git a/src/dtype.rs b/src/dtype.rs index dc08ba84e..7218748af 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -20,7 +20,7 @@ pub use num_complex::{Complex32, Complex64}; /// .unwrap() /// .downcast() /// .unwrap(); -/// assert!(dtype.is_equiv_to(numpy::PyArrayDescr::of::(py))); +/// assert!(dtype.is_equiv_to(numpy::dtype::(py))); /// }); /// ``` pub struct PyArrayDescr(PyAny); @@ -39,6 +39,11 @@ unsafe fn arraydescr_check(op: *mut ffi::PyObject) -> c_int { ) } +/// Returns the type descriptor ("dtype") for a registered type. +pub fn dtype(py: Python) -> &PyArrayDescr { + T::get_dtype(py) +} + impl PyArrayDescr { /// Returns `self` as `*mut PyArray_Descr`. pub fn as_dtype_ptr(&self) -> *mut PyArray_Descr { @@ -72,7 +77,7 @@ impl PyArrayDescr { Self::from_npy_type(py, NPY_TYPES::NPY_OBJECT) } - /// Returns the type descriptor for a registered type. + /// Returns the type descriptor ("dtype") for a registered type. pub fn of(py: Python) -> &Self { T::get_dtype(py) } @@ -251,12 +256,12 @@ unsafe impl Element for PyObject { mod tests { use std::mem::size_of; - use super::{Complex32, Complex64, Element, PyArrayDescr}; + use super::{dtype, Complex32, Complex64, Element}; #[test] fn test_dtype_names() { fn type_name(py: pyo3::Python) -> &str { - PyArrayDescr::of::(py).get_type().name().unwrap() + dtype::(py).get_type().name().unwrap() } pyo3::Python::with_gil(|py| { assert_eq!(type_name::(py), "bool_"); diff --git a/src/lib.rs b/src/lib.rs index 227e126bb..5b3c77a6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ pub use crate::array::{ PyArray6, PyArrayDyn, }; pub use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; -pub use crate::dtype::{Complex32, Complex64, Element, PyArrayDescr}; +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::{ diff --git a/tests/array.rs b/tests/array.rs index 95db816df..3997b9ea1 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -249,7 +249,7 @@ fn dtype_from_py() { .downcast() .unwrap(); assert_eq!(&format!("{:?}", dtype), "dtype('uint32')"); - assert!(dtype.is_equiv_to(numpy::PyArrayDescr::of::(py))); + assert!(dtype.is_equiv_to(numpy::dtype::(py))); }) } From fee612df9cc0b3205f3406d1b63e3be19a0e6526 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 12:49:58 +0000 Subject: [PATCH 20/21] (Use `cfg` instead of `size_of::` in tests) --- src/dtype.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/dtype.rs b/src/dtype.rs index 7218748af..e7b744917 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -254,8 +254,6 @@ unsafe impl Element for PyObject { #[cfg(test)] mod tests { - use std::mem::size_of; - use super::{dtype, Complex32, Complex64, Element}; #[test] @@ -277,16 +275,15 @@ mod tests { assert_eq!(type_name::(py), "float64"); assert_eq!(type_name::(py), "complex64"); assert_eq!(type_name::(py), "complex128"); - match size_of::() { - 32 => { - assert_eq!(type_name::(py), "uint32"); - assert_eq!(type_name::(py), "int32"); - } - 64 => { - assert_eq!(type_name::(py), "uint64"); - assert_eq!(type_name::(py), "int64"); - } - _ => {} + #[cfg(target_pointer_width = "32")] + { + assert_eq!(type_name::(py), "uint32"); + assert_eq!(type_name::(py), "int32"); + } + #[cfg(target_pointer_width = "64")] + { + assert_eq!(type_name::(py), "uint64"); + assert_eq!(type_name::(py), "int64"); } }); } From 4021797a33500bfb1faa3ab65e7814c5a6c8a264 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jan 2022 13:25:48 +0000 Subject: [PATCH 21/21] Update the changelog (descriptors rework) --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aedebfaf..f5dec9b3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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))