Skip to content
New issue

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

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

Already on GitHub? # to your account

NumPy 2 compatibility #12

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ def finalize_options(self):
long_description=long_description,
long_description_content_type='text/markdown',
packages=['loris'],
setup_requires=['numpy'],
install_requires=['aedat', 'numpy', 'tqdm'],
setup_requires=['numpy>=1.24'],
install_requires=['aedat', 'numpy>=1.24', 'tqdm'],
classifiers=[
'Programming Language :: Python :: 3',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
Expand Down
3 changes: 2 additions & 1 deletion source/cpp/loris_extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <Python.h>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>
#include "npy_2_compat.h"

/// count determines the number of events in the given stream.
template <sepia::type event_stream_type>
Expand Down Expand Up @@ -238,7 +239,7 @@ PyArrayObject* get_events(PyObject* dict) {
}
const auto descriptions = get_descriptions<event_stream_type>();
const auto offsets = get_offsets<event_stream_type>();
auto fields = PyArray_DESCR(events)->fields;
auto fields = PyDataType_FIELDS(PyArray_DESCR(events));
if (!PyMapping_Check(fields)) {
throw std::runtime_error("'events' must be a structured array");
}
Expand Down
249 changes: 249 additions & 0 deletions source/cpp/npy_2_compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
* This header file defines relevant features which:
* - Require runtime inspection depending on the NumPy version.
* - May be needed when compiling with an older version of NumPy to allow
* a smooth transition.
*
* As such, it is shipped with NumPy 2.0, but designed to be vendored in full
* or parts by downstream projects.
*
* It must be included after any other includes. `import_array()` must have
* been called in the scope or version dependency will misbehave, even when
* only `PyUFunc_` API is used.
*
* If required complicated defs (with inline functions) should be written as:
*
* #if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
* Simple definition when NumPy 2.0 API is guaranteed.
* #else
* static inline definition of a 1.x compatibility shim
* #if NPY_ABI_VERSION < 0x02000000
* Make 1.x compatibility shim the public API (1.x only branch)
* #else
* Runtime dispatched version (1.x or 2.x)
* #endif
* #endif
*
* An internal build always passes NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
*/

#ifndef NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_
#define NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_

/*
* New macros for accessing real and complex part of a complex number can be
* found in "npy_2_complexcompat.h".
*/


/*
* This header is meant to be included by downstream directly for 1.x compat.
* In that case we need to ensure that users first included the full headers
* and not just `ndarraytypes.h`.
*/

#ifndef NPY_FEATURE_VERSION
#error "The NumPy 2 compat header requires `import_array()` for which " \
"the `ndarraytypes.h` header include is not sufficient. Please " \
"include it after `numpy/ndarrayobject.h` or similar.\n" \
"To simplify inclusion, you may use `PyArray_ImportNumPy()` " \
"which is defined in the compat header and is lightweight (can be)."
#endif

#if NPY_ABI_VERSION < 0x02000000
/*
* Define 2.0 feature version as it is needed below to decide whether we
* compile for both 1.x and 2.x (defining it gaurantees 1.x only).
*/
#define NPY_2_0_API_VERSION 0x00000012
/*
* If we are compiling with NumPy 1.x, PyArray_RUNTIME_VERSION so we
* pretend the `PyArray_RUNTIME_VERSION` is `NPY_FEATURE_VERSION`.
* This allows downstream to use `PyArray_RUNTIME_VERSION` if they need to.
*/
#define PyArray_RUNTIME_VERSION NPY_FEATURE_VERSION
/* Compiling on NumPy 1.x where these are the same: */
#define PyArray_DescrProto PyArray_Descr
#endif


/*
* Define a better way to call `_import_array()` to simplify backporting as
* we now require imports more often (necessary to make ABI flexible).
*/
#ifdef import_array1

static inline int
PyArray_ImportNumPyAPI()
{
if (NPY_UNLIKELY(PyArray_API == NULL)) {
import_array1(-1);
}
return 0;
}

#endif /* import_array1 */


/*
* NPY_DEFAULT_INT
*
* The default integer has changed, `NPY_DEFAULT_INT` is available at runtime
* for use as type number, e.g. `PyArray_DescrFromType(NPY_DEFAULT_INT)`.
*
* NPY_RAVEL_AXIS
*
* This was introduced in NumPy 2.0 to allow indicating that an axis should be
* raveled in an operation. Before NumPy 2.0, NPY_MAXDIMS was used for this purpose.
*
* NPY_MAXDIMS
*
* A constant indicating the maximum number dimensions allowed when creating
* an ndarray.
*
* NPY_NTYPES_LEGACY
*
* The number of built-in NumPy dtypes.
*/
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
#define NPY_DEFAULT_INT NPY_INTP
#define NPY_RAVEL_AXIS NPY_MIN_INT
#define NPY_MAXARGS 64

#elif NPY_ABI_VERSION < 0x02000000
#define NPY_DEFAULT_INT NPY_LONG
#define NPY_RAVEL_AXIS 32
#define NPY_MAXARGS 32

/* Aliases of 2.x names to 1.x only equivalent names */
#define NPY_NTYPES NPY_NTYPES_LEGACY
#define PyArray_DescrProto PyArray_Descr
#define _PyArray_LegacyDescr PyArray_Descr
/* NumPy 2 definition always works, but add it for 1.x only */
#define PyDataType_ISLEGACY(dtype) (1)
#else
#define NPY_DEFAULT_INT \
(PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_INTP : NPY_LONG)
#define NPY_RAVEL_AXIS \
(PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? -1 : 32)
#define NPY_MAXARGS \
(PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? 64 : 32)
#endif


/*
* Access inline functions for descriptor fields. Except for the first
* few fields, these needed to be moved (elsize, alignment) for
* additional space. Or they are descriptor specific and are not generally
* available anymore (metadata, c_metadata, subarray, names, fields).
*
* Most of these are defined via the `DESCR_ACCESSOR` macro helper.
*/
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION || NPY_ABI_VERSION < 0x02000000
/* Compiling for 1.x or 2.x only, direct field access is OK: */

static inline void
PyDataType_SET_ELSIZE(PyArray_Descr *dtype, npy_intp size)
{
dtype->elsize = size;
}

static inline npy_uint64
PyDataType_FLAGS(const PyArray_Descr *dtype)
{
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
return dtype->flags;
#else
return (unsigned char)dtype->flags; /* Need unsigned cast on 1.x */
#endif
}

#define DESCR_ACCESSOR(FIELD, field, type, legacy_only) \
static inline type \
PyDataType_##FIELD(const PyArray_Descr *dtype) { \
if (legacy_only && !PyDataType_ISLEGACY(dtype)) { \
return (type)0; \
} \
return ((_PyArray_LegacyDescr *)dtype)->field; \
}
#else /* compiling for both 1.x and 2.x */

static inline void
PyDataType_SET_ELSIZE(PyArray_Descr *dtype, npy_intp size)
{
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) {
((_PyArray_DescrNumPy2 *)dtype)->elsize = size;
}
else {
((PyArray_DescrProto *)dtype)->elsize = (int)size;
}
}

static inline npy_uint64
PyDataType_FLAGS(const PyArray_Descr *dtype)
{
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) {
return ((_PyArray_DescrNumPy2 *)dtype)->flags;
}
else {
return (unsigned char)((PyArray_DescrProto *)dtype)->flags;
}
}

/* Cast to LegacyDescr always fine but needed when `legacy_only` */
#define DESCR_ACCESSOR(FIELD, field, type, legacy_only) \
static inline type \
PyDataType_##FIELD(const PyArray_Descr *dtype) { \
if (legacy_only && !PyDataType_ISLEGACY(dtype)) { \
return (type)0; \
} \
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { \
return ((_PyArray_LegacyDescr *)dtype)->field; \
} \
else { \
return ((PyArray_DescrProto *)dtype)->field; \
} \
}
#endif

DESCR_ACCESSOR(ELSIZE, elsize, npy_intp, 0)
DESCR_ACCESSOR(ALIGNMENT, alignment, npy_intp, 0)
DESCR_ACCESSOR(METADATA, metadata, PyObject *, 1)
DESCR_ACCESSOR(SUBARRAY, subarray, PyArray_ArrayDescr *, 1)
DESCR_ACCESSOR(NAMES, names, PyObject *, 1)
DESCR_ACCESSOR(FIELDS, fields, PyObject *, 1)
DESCR_ACCESSOR(C_METADATA, c_metadata, NpyAuxData *, 1)

#undef DESCR_ACCESSOR


#if !(defined(NPY_INTERNAL_BUILD) && NPY_INTERNAL_BUILD)
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
static inline PyArray_ArrFuncs *
PyDataType_GetArrFuncs(const PyArray_Descr *descr)
{
return _PyDataType_GetArrFuncs(descr);
}
#elif NPY_ABI_VERSION < 0x02000000
static inline PyArray_ArrFuncs *
PyDataType_GetArrFuncs(const PyArray_Descr *descr)
{
return descr->f;
}
#else
static inline PyArray_ArrFuncs *
PyDataType_GetArrFuncs(const PyArray_Descr *descr)
{
if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) {
return _PyDataType_GetArrFuncs(descr);
}
else {
return ((PyArray_DescrProto *)descr)->f;
}
}
#endif


#endif /* not internal build */

#endif /* NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ */