summaryrefslogtreecommitdiff
path: root/numpy/core
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/core')
-rw-r--r--numpy/core/include/numpy/experimental_dtype_api.h143
-rw-r--r--numpy/core/setup.py1
-rw-r--r--numpy/core/src/multiarray/array_method.c28
-rw-r--r--numpy/core/src/multiarray/array_method.h61
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c12
-rw-r--r--numpy/core/src/multiarray/dtype_transfer.c8
-rw-r--r--numpy/core/src/multiarray/dtype_transfer.h4
-rw-r--r--numpy/core/src/multiarray/experimental_public_dtype_api.c71
-rw-r--r--numpy/core/src/umath/legacy_array_method.c2
-rw-r--r--numpy/core/src/umath/legacy_array_method.h2
-rw-r--r--numpy/core/src/umath/wrapping_array_method.c301
11 files changed, 594 insertions, 39 deletions
diff --git a/numpy/core/include/numpy/experimental_dtype_api.h b/numpy/core/include/numpy/experimental_dtype_api.h
index 88565cd52..1dd6215e6 100644
--- a/numpy/core/include/numpy/experimental_dtype_api.h
+++ b/numpy/core/include/numpy/experimental_dtype_api.h
@@ -24,6 +24,12 @@
* Register a new loop for a ufunc. This uses the `PyArrayMethod_Spec`
* which must be filled in (see in-line comments).
*
+ * - PyUFunc_AddWrappingLoop:
+ *
+ * Register a new loop which reuses an existing one, but modifies the
+ * result dtypes. Please search the internal NumPy docs for more info
+ * at this point. (Used for physical units dtype.)
+ *
* - PyUFunc_AddPromoter:
*
* Register a new promoter for a ufunc. A promoter is a function stored
@@ -58,6 +64,16 @@
* also promote C; where "promotes" means implements the promotion.
* (There are some exceptions for abstract DTypes)
*
+ * - PyArray_GetDefaultDescr:
+ *
+ * Given a DType class, returns the default instance (descriptor).
+ * This is an inline function checking for `singleton` first and only
+ * calls the `default_descr` function if necessary.
+ *
+ * - PyArray_DoubleDType, etc.:
+ *
+ * Aliases to the DType classes for the builtin NumPy DTypes.
+ *
* WARNING
* =======
*
@@ -101,20 +117,41 @@
/*
- * Just a hack so I don't forget importing as much myself, I spend way too
- * much time noticing it the first time around :).
+ * There must be a better way?! -- Oh well, this is experimental
+ * (my issue with it, is that I cannot undef those helpers).
*/
-static void
-__not_imported(void)
-{
- printf("*****\nCritical error, dtype API not imported\n*****\n");
-}
-static void *__uninitialized_table[] = {
- &__not_imported, &__not_imported, &__not_imported, &__not_imported,
- &__not_imported, &__not_imported, &__not_imported, &__not_imported};
+#if defined(PY_ARRAY_UNIQUE_SYMBOL)
+ #define NPY_EXP_DTYPE_API_CONCAT_HELPER2(x, y) x ## y
+ #define NPY_EXP_DTYPE_API_CONCAT_HELPER(arg) NPY_EXP_DTYPE_API_CONCAT_HELPER2(arg, __experimental_dtype_api_table)
+ #define __experimental_dtype_api_table NPY_EXP_DTYPE_API_CONCAT_HELPER(PY_ARRAY_UNIQUE_SYMBOL)
+#else
+ #define __experimental_dtype_api_table __experimental_dtype_api_table
+#endif
+
+/* Support for correct multi-file projects: */
+#if defined(NO_IMPORT) || defined(NO_IMPORT_ARRAY)
+ extern void **__experimental_dtype_api_table;
+#else
+ /*
+ * Just a hack so I don't forget importing as much myself, I spend way too
+ * much time noticing it the first time around :).
+ */
+ static void
+ __not_imported(void)
+ {
+ printf("*****\nCritical error, dtype API not imported\n*****\n");
+ }
+ static void *__uninitialized_table[] = {
+ &__not_imported, &__not_imported, &__not_imported, &__not_imported,
+ &__not_imported, &__not_imported, &__not_imported, &__not_imported};
-static void **__experimental_dtype_api_table = __uninitialized_table;
+ #if defined(PY_ARRAY_UNIQUE_SYMBOL)
+ void **__experimental_dtype_api_table = __uninitialized_table;
+ #else
+ static void **__experimental_dtype_api_table = __uninitialized_table;
+ #endif
+#endif
/*
@@ -172,7 +209,7 @@ typedef struct {
int nin, nout;
NPY_CASTING casting;
NPY_ARRAYMETHOD_FLAGS flags;
- PyObject **dtypes; /* array of DType class objects */
+ PyArray_DTypeMeta **dtypes;
PyType_Slot *slots;
} PyArrayMethod_Spec;
@@ -187,6 +224,21 @@ typedef PyObject *_ufunc_addloop_fromspec_func(
(*(_ufunc_addloop_fromspec_func *)(__experimental_dtype_api_table[0]))
+/* Please see the NumPy definitions in `array_method.h` for details on these */
+typedef int translate_given_descrs_func(int nin, int nout,
+ PyArray_DTypeMeta *wrapped_dtypes[],
+ PyArray_Descr *given_descrs[], PyArray_Descr *new_descrs[]);
+typedef int translate_loop_descrs_func(int nin, int nout,
+ PyArray_DTypeMeta *new_dtypes[], PyArray_Descr *given_descrs[],
+ PyArray_Descr *original_descrs[], PyArray_Descr *loop_descrs[]);
+
+typedef int _ufunc_wrapping_loop_func(PyObject *ufunc_obj,
+ PyArray_DTypeMeta *new_dtypes[], PyArray_DTypeMeta *wrapped_dtypes[],
+ translate_given_descrs_func *translate_given_descrs,
+ translate_loop_descrs_func *translate_loop_descrs);
+#define PyUFunc_AddWrappingLoop \
+ (*(_ufunc_wrapping_loop_func *)(__experimental_dtype_api_table[7]))
+
/*
* Type of the C promoter function, which must be wrapped into a
* PyCapsule with name "numpy._ufunc_promoter".
@@ -334,6 +386,65 @@ typedef PyArray_DTypeMeta *__promote_dtype_sequence(
((__promote_dtype_sequence *)(__experimental_dtype_api_table[5]))
+typedef PyArray_Descr *__get_default_descr(
+ PyArray_DTypeMeta *DType);
+#define _PyArray_GetDefaultDescr \
+ ((__get_default_descr *)(__experimental_dtype_api_table[6]))
+
+static NPY_INLINE PyArray_Descr *
+PyArray_GetDefaultDescr(PyArray_DTypeMeta *DType)
+{
+ if (DType->singleton != NULL) {
+ Py_INCREF(DType->singleton);
+ return DType->singleton;
+ }
+ return _PyArray_GetDefaultDescr(DType);
+}
+
+
+/*
+ * NumPy's builtin DTypes:
+ */
+#define PyArray_BoolDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[10])
+/* Integers */
+#define PyArray_ByteDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[11])
+#define PyArray_UByteDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[12])
+#define PyArray_ShortDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[13])
+#define PyArray_UShortDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[14])
+#define PyArray_IntDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[15])
+#define PyArray_UIntDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[16])
+#define PyArray_LongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[17])
+#define PyArray_ULongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[18])
+#define PyArray_LongLongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[19])
+#define PyArray_ULongLongDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[20])
+/* Integer aliases */
+#define PyArray_Int8Type (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[21])
+#define PyArray_UInt8DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[22])
+#define PyArray_Int16DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[23])
+#define PyArray_UInt16DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[24])
+#define PyArray_Int32DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[25])
+#define PyArray_UInt32DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[26])
+#define PyArray_Int64DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[27])
+#define PyArray_UInt64DType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[28])
+#define PyArray_IntpDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[29])
+#define PyArray_UIntpDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[30])
+/* Floats */
+#define PyArray_HalfType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[31])
+#define PyArray_FloatDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[32])
+#define PyArray_DoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[33])
+#define PyArray_LongDoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[34])
+/* Complex */
+#define PyArray_CFloatDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[35])
+#define PyArray_CDoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[36])
+#define PyArray_CLongDoubleDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[37])
+/* String/Bytes */
+#define PyArray_StringDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[38])
+#define PyArray_UnicodeDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[39])
+/* Datetime/Timedelta */
+#define PyArray_DatetimeDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[40])
+#define PyArray_TimedeltaDType (*(PyArray_DTypeMeta *)__experimental_dtype_api_table[41])
+
+
/*
* ********************************
* Initialization
@@ -344,7 +455,9 @@ typedef PyArray_DTypeMeta *__promote_dtype_sequence(
* runtime-check this.
* You must call this function to use the symbols defined in this file.
*/
-#define __EXPERIMENTAL_DTYPE_VERSION 2
+#if !defined(NO_IMPORT) && !defined(NO_IMPORT_ARRAY)
+
+#define __EXPERIMENTAL_DTYPE_VERSION 4
static int
import_experimental_dtype_api(int version)
@@ -372,7 +485,7 @@ import_experimental_dtype_api(int version)
if (api == NULL) {
return -1;
}
- __experimental_dtype_api_table = PyCapsule_GetPointer(api,
+ __experimental_dtype_api_table = (void **)PyCapsule_GetPointer(api,
"experimental_dtype_api_table");
Py_DECREF(api);
@@ -383,4 +496,6 @@ import_experimental_dtype_api(int version)
return 0;
}
+#endif /* !defined(NO_IMPORT) && !defined(NO_IMPORT_ARRAY) */
+
#endif /* NUMPY_CORE_INCLUDE_NUMPY_EXPERIMENTAL_DTYPE_API_H_ */
diff --git a/numpy/core/setup.py b/numpy/core/setup.py
index c97747606..63962ab79 100644
--- a/numpy/core/setup.py
+++ b/numpy/core/setup.py
@@ -1009,6 +1009,7 @@ def configuration(parent_package='',top_path=None):
join('src', 'umath', 'clip.cpp'),
join('src', 'umath', 'dispatching.c'),
join('src', 'umath', 'legacy_array_method.c'),
+ join('src', 'umath', 'wrapping_array_method.c'),
join('src', 'umath', 'ufunc_object.c'),
join('src', 'umath', 'extobj.c'),
join('src', 'umath', 'scalarmath.c.src'),
diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c
index b421d9e4f..e3436c573 100644
--- a/numpy/core/src/multiarray/array_method.c
+++ b/numpy/core/src/multiarray/array_method.c
@@ -133,7 +133,7 @@ is_contiguous(
NPY_NO_EXPORT int
npy_default_get_strided_loop(
PyArrayMethod_Context *context,
- int aligned, int NPY_UNUSED(move_references), npy_intp *strides,
+ int aligned, int NPY_UNUSED(move_references), const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
{
@@ -261,12 +261,17 @@ fill_arraymethod_from_slots(
meth->resolve_descriptors = slot->pfunc;
continue;
case NPY_METH_get_loop:
- if (private) {
- /* Only allow override for private functions initially */
- meth->get_strided_loop = slot->pfunc;
- continue;
- }
- break;
+ /*
+ * NOTE: get_loop is considered "unstable" in the public API,
+ * I do not like the signature, and the `move_references`
+ * parameter must NOT be used.
+ * (as in: we should not worry about changing it, but of
+ * course that would not break it immediately.)
+ */
+ /* Only allow override for private functions initially */
+ meth->get_strided_loop = slot->pfunc;
+ continue;
+ /* "Typical" loops, supported used by the default `get_loop` */
case NPY_METH_strided_loop:
meth->strided_loop = slot->pfunc;
continue;
@@ -460,6 +465,15 @@ arraymethod_dealloc(PyObject *self)
PyMem_Free(meth->name);
+ if (meth->wrapped_meth != NULL) {
+ /* Cleanup for wrapping array method (defined in umath) */
+ Py_DECREF(meth->wrapped_meth);
+ for (int i = 0; i < meth->nin + meth->nout; i++) {
+ Py_XDECREF(meth->wrapped_dtypes[i]);
+ }
+ PyMem_Free(meth->wrapped_dtypes);
+ }
+
Py_TYPE(self)->tp_free(self);
}
diff --git a/numpy/core/src/multiarray/array_method.h b/numpy/core/src/multiarray/array_method.h
index 35b9033e0..30dd94a80 100644
--- a/numpy/core/src/multiarray/array_method.h
+++ b/numpy/core/src/multiarray/array_method.h
@@ -77,13 +77,65 @@ typedef NPY_CASTING (resolve_descriptors_function)(
typedef int (get_loop_function)(
PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *strides,
+ const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
/*
+ * The following functions are only used be the wrapping array method defined
+ * in umath/wrapping_array_method.c
+ */
+
+/*
+ * The function to convert the given descriptors (passed in to
+ * `resolve_descriptors`) and translates them for the wrapped loop.
+ * The new descriptors MUST be viewable with the old ones, `NULL` must be
+ * supported (for outputs) and should normally be forwarded.
+ *
+ * The function must clean up on error.
+ *
+ * NOTE: We currently assume that this translation gives "viewable" results.
+ * I.e. there is no additional casting related to the wrapping process.
+ * In principle that could be supported, but not sure it is useful.
+ * This currently also means that e.g. alignment must apply identically
+ * to the new dtypes.
+ *
+ * TODO: Due to the fact that `resolve_descriptors` is also used for `can_cast`
+ * there is no way to "pass out" the result of this function. This means
+ * it will be called twice for every ufunc call.
+ * (I am considering including `auxdata` as an "optional" parameter to
+ * `resolve_descriptors`, so that it can be filled there if not NULL.)
+ */
+typedef int translate_given_descrs_func(int nin, int nout,
+ PyArray_DTypeMeta *wrapped_dtypes[],
+ PyArray_Descr *given_descrs[], PyArray_Descr *new_descrs[]);
+
+/**
+ * The function to convert the actual loop descriptors (as returned by the
+ * original `resolve_descriptors` function) to the ones the output array
+ * should use.
+ * This function must return "viewable" types, it must not mutate them in any
+ * form that would break the inner-loop logic. Does not need to support NULL.
+ *
+ * The function must clean up on error.
+ *
+ * @param nargs Number of arguments
+ * @param new_dtypes The DTypes of the output (usually probably not needed)
+ * @param given_descrs Original given_descrs to the resolver, necessary to
+ * fetch any information related to the new dtypes from the original.
+ * @param original_descrs The `loop_descrs` returned by the wrapped loop.
+ * @param loop_descrs The output descriptors, compatible to `original_descrs`.
+ *
+ * @returns 0 on success, -1 on failure.
+ */
+typedef int translate_loop_descrs_func(int nin, int nout,
+ PyArray_DTypeMeta *new_dtypes[], PyArray_Descr *given_descrs[],
+ PyArray_Descr *original_descrs[], PyArray_Descr *loop_descrs[]);
+
+
+/*
* This struct will be public and necessary for creating a new ArrayMethod
* object (casting and ufuncs).
* We could version the struct, although since we allow passing arbitrary
@@ -125,6 +177,11 @@ typedef struct PyArrayMethodObject_tag {
PyArrayMethod_StridedLoop *contiguous_loop;
PyArrayMethod_StridedLoop *unaligned_strided_loop;
PyArrayMethod_StridedLoop *unaligned_contiguous_loop;
+ /* Chunk only used for wrapping array method defined in umath */
+ struct PyArrayMethodObject_tag *wrapped_meth;
+ PyArray_DTypeMeta **wrapped_dtypes;
+ translate_given_descrs_func *translate_given_descrs;
+ translate_loop_descrs_func *translate_loop_descrs;
} PyArrayMethodObject;
@@ -167,7 +224,7 @@ extern NPY_NO_EXPORT PyTypeObject PyBoundArrayMethod_Type;
NPY_NO_EXPORT int
npy_default_get_strided_loop(
PyArrayMethod_Context *context,
- int aligned, int NPY_UNUSED(move_references), npy_intp *strides,
+ int aligned, int NPY_UNUSED(move_references), const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 5387a0970..b8d443752 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -2458,7 +2458,7 @@ get_byteswap_loop(
NPY_NO_EXPORT int
complex_to_noncomplex_get_loop(
PyArrayMethod_Context *context,
- int aligned, int move_references, npy_intp *strides,
+ int aligned, int move_references, const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
{
@@ -2799,7 +2799,7 @@ string_to_string_resolve_descriptors(
NPY_NO_EXPORT int
string_to_string_get_loop(
PyArrayMethod_Context *context,
- int aligned, int NPY_UNUSED(move_references), npy_intp *strides,
+ int aligned, int NPY_UNUSED(move_references), const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
{
@@ -3048,7 +3048,7 @@ static int
nonstructured_to_structured_get_loop(
PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *strides,
+ const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
@@ -3210,7 +3210,7 @@ static int
structured_to_nonstructured_get_loop(
PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *strides,
+ const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
@@ -3510,7 +3510,7 @@ NPY_NO_EXPORT int
void_to_void_get_loop(
PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *strides,
+ const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
@@ -3726,7 +3726,7 @@ static int
object_to_object_get_loop(
PyArrayMethod_Context *NPY_UNUSED(context),
int NPY_UNUSED(aligned), int move_references,
- npy_intp *NPY_UNUSED(strides),
+ const npy_intp *NPY_UNUSED(strides),
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c
index b0db94817..91b8aac98 100644
--- a/numpy/core/src/multiarray/dtype_transfer.c
+++ b/numpy/core/src/multiarray/dtype_transfer.c
@@ -237,7 +237,7 @@ NPY_NO_EXPORT int
any_to_object_get_loop(
PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *strides,
+ const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
@@ -343,7 +343,7 @@ NPY_NO_EXPORT int
object_to_any_get_loop(
PyArrayMethod_Context *context,
int NPY_UNUSED(aligned), int move_references,
- npy_intp *NPY_UNUSED(strides),
+ const npy_intp *NPY_UNUSED(strides),
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
@@ -3023,9 +3023,9 @@ init_cast_info(
if (!PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"Cannot cast array data from %R to %R.", src_dtype, dst_dtype);
- Py_DECREF(meth);
- return -1;
}
+ Py_DECREF(meth);
+ return -1;
}
assert(PyArray_DescrCheck(cast_info->descriptors[0]));
assert(PyArray_DescrCheck(cast_info->descriptors[1]));
diff --git a/numpy/core/src/multiarray/dtype_transfer.h b/numpy/core/src/multiarray/dtype_transfer.h
index c7e0a029f..9ae332e38 100644
--- a/numpy/core/src/multiarray/dtype_transfer.h
+++ b/numpy/core/src/multiarray/dtype_transfer.h
@@ -132,7 +132,7 @@ NPY_NO_EXPORT int
any_to_object_get_loop(
PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *strides,
+ const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
@@ -141,7 +141,7 @@ NPY_NO_EXPORT int
object_to_any_get_loop(
PyArrayMethod_Context *context,
int NPY_UNUSED(aligned), int move_references,
- npy_intp *NPY_UNUSED(strides),
+ const npy_intp *NPY_UNUSED(strides),
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
diff --git a/numpy/core/src/multiarray/experimental_public_dtype_api.c b/numpy/core/src/multiarray/experimental_public_dtype_api.c
index e9d191002..cf5f152ab 100644
--- a/numpy/core/src/multiarray/experimental_public_dtype_api.c
+++ b/numpy/core/src/multiarray/experimental_public_dtype_api.c
@@ -16,7 +16,7 @@
#include "common_dtype.h"
-#define EXPERIMENTAL_DTYPE_API_VERSION 3
+#define EXPERIMENTAL_DTYPE_API_VERSION 4
typedef struct{
@@ -332,6 +332,16 @@ PyUFunc_AddLoopFromSpec(PyObject *ufunc, PyArrayMethod_Spec *spec)
return PyUFunc_AddLoop((PyUFuncObject *)ufunc, info, 0);
}
+/*
+ * Function is defined in umath/wrapping_array_method.c
+ * (same/one compilation unit)
+ */
+NPY_NO_EXPORT int
+PyUFunc_AddWrappingLoop(PyObject *ufunc_obj,
+ PyArray_DTypeMeta *new_dtypes[], PyArray_DTypeMeta *wrapped_dtypes[],
+ translate_given_descrs_func *translate_given_descrs,
+ translate_loop_descrs_func *translate_loop_descrs);
+
static int
PyUFunc_AddPromoter(
@@ -358,18 +368,75 @@ PyUFunc_AddPromoter(
}
+/*
+ * Lightweight function fetch a default instance of a DType class.
+ * Note that this version is named `_PyArray_GetDefaultDescr` with an
+ * underscore. The `singleton` slot is public, so an inline version is
+ * provided that checks `singleton != NULL` first.
+ */
+static PyArray_Descr *
+_PyArray_GetDefaultDescr(PyArray_DTypeMeta *DType)
+{
+ return NPY_DT_CALL_default_descr(DType);
+}
+
+
NPY_NO_EXPORT PyObject *
_get_experimental_dtype_api(PyObject *NPY_UNUSED(mod), PyObject *arg)
{
- static void *experimental_api_table[] = {
+ static void *experimental_api_table[42] = {
&PyUFunc_AddLoopFromSpec,
&PyUFunc_AddPromoter,
&PyArrayDTypeMeta_Type,
&PyArrayInitDTypeMeta_FromSpec,
&PyArray_CommonDType,
&PyArray_PromoteDTypeSequence,
+ &_PyArray_GetDefaultDescr,
+ &PyUFunc_AddWrappingLoop,
+ NULL,
NULL,
+ /* NumPy's builtin DTypes (starting at offset 10 going to 41) */
};
+ if (experimental_api_table[10] == NULL) {
+ experimental_api_table[10] = PyArray_DTypeFromTypeNum(NPY_BOOL);
+ /* Integers */
+ experimental_api_table[11] = PyArray_DTypeFromTypeNum(NPY_BYTE);
+ experimental_api_table[12] = PyArray_DTypeFromTypeNum(NPY_UBYTE);
+ experimental_api_table[13] = PyArray_DTypeFromTypeNum(NPY_SHORT);
+ experimental_api_table[14] = PyArray_DTypeFromTypeNum(NPY_USHORT);
+ experimental_api_table[15] = PyArray_DTypeFromTypeNum(NPY_INT);
+ experimental_api_table[16] = PyArray_DTypeFromTypeNum(NPY_UINT);
+ experimental_api_table[17] = PyArray_DTypeFromTypeNum(NPY_LONG);
+ experimental_api_table[18] = PyArray_DTypeFromTypeNum(NPY_ULONG);
+ experimental_api_table[19] = PyArray_DTypeFromTypeNum(NPY_LONGLONG);
+ experimental_api_table[20] = PyArray_DTypeFromTypeNum(NPY_ULONGLONG);
+ /* Integer aliases */
+ experimental_api_table[21] = PyArray_DTypeFromTypeNum(NPY_INT8);
+ experimental_api_table[22] = PyArray_DTypeFromTypeNum(NPY_UINT8);
+ experimental_api_table[23] = PyArray_DTypeFromTypeNum(NPY_INT16);
+ experimental_api_table[24] = PyArray_DTypeFromTypeNum(NPY_UINT16);
+ experimental_api_table[25] = PyArray_DTypeFromTypeNum(NPY_INT32);
+ experimental_api_table[26] = PyArray_DTypeFromTypeNum(NPY_UINT32);
+ experimental_api_table[27] = PyArray_DTypeFromTypeNum(NPY_INT64);
+ experimental_api_table[28] = PyArray_DTypeFromTypeNum(NPY_UINT64);
+ experimental_api_table[29] = PyArray_DTypeFromTypeNum(NPY_INTP);
+ experimental_api_table[30] = PyArray_DTypeFromTypeNum(NPY_UINTP);
+ /* Floats */
+ experimental_api_table[31] = PyArray_DTypeFromTypeNum(NPY_HALF);
+ experimental_api_table[32] = PyArray_DTypeFromTypeNum(NPY_FLOAT);
+ experimental_api_table[33] = PyArray_DTypeFromTypeNum(NPY_DOUBLE);
+ experimental_api_table[34] = PyArray_DTypeFromTypeNum(NPY_LONGDOUBLE);
+ /* Complex */
+ experimental_api_table[35] = PyArray_DTypeFromTypeNum(NPY_CFLOAT);
+ experimental_api_table[36] = PyArray_DTypeFromTypeNum(NPY_CDOUBLE);
+ experimental_api_table[37] = PyArray_DTypeFromTypeNum(NPY_CLONGDOUBLE);
+ /* String/Bytes */
+ experimental_api_table[38] = PyArray_DTypeFromTypeNum(NPY_STRING);
+ experimental_api_table[39] = PyArray_DTypeFromTypeNum(NPY_UNICODE);
+ /* Datetime/Timedelta */
+ experimental_api_table[40] = PyArray_DTypeFromTypeNum(NPY_DATETIME);
+ experimental_api_table[41] = PyArray_DTypeFromTypeNum(NPY_TIMEDELTA);
+ }
char *env = getenv("NUMPY_EXPERIMENTAL_DTYPE_API");
if (env == NULL || strcmp(env, "1") != 0) {
diff --git a/numpy/core/src/umath/legacy_array_method.c b/numpy/core/src/umath/legacy_array_method.c
index f4b2aed96..171b53efd 100644
--- a/numpy/core/src/umath/legacy_array_method.c
+++ b/numpy/core/src/umath/legacy_array_method.c
@@ -192,7 +192,7 @@ simple_legacy_resolve_descriptors(
NPY_NO_EXPORT int
get_wrapped_legacy_ufunc_loop(PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *NPY_UNUSED(strides),
+ const npy_intp *NPY_UNUSED(strides),
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
diff --git a/numpy/core/src/umath/legacy_array_method.h b/numpy/core/src/umath/legacy_array_method.h
index d20b4fb08..498fb1aa2 100644
--- a/numpy/core/src/umath/legacy_array_method.h
+++ b/numpy/core/src/umath/legacy_array_method.h
@@ -20,7 +20,7 @@ PyArray_NewLegacyWrappingArrayMethod(PyUFuncObject *ufunc,
NPY_NO_EXPORT int
get_wrapped_legacy_ufunc_loop(PyArrayMethod_Context *context,
int aligned, int move_references,
- npy_intp *NPY_UNUSED(strides),
+ const npy_intp *NPY_UNUSED(strides),
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
diff --git a/numpy/core/src/umath/wrapping_array_method.c b/numpy/core/src/umath/wrapping_array_method.c
new file mode 100644
index 000000000..9f8f036e8
--- /dev/null
+++ b/numpy/core/src/umath/wrapping_array_method.c
@@ -0,0 +1,301 @@
+/*
+ * This file defines most of the machinery in order to wrap an existing ufunc
+ * loop for use with a different set of dtypes.
+ *
+ * There are two approaches for this, one is to teach the NumPy core about
+ * the possibility that the loop descriptors do not match exactly the result
+ * descriptors.
+ * The other is to handle this fully by "wrapping", so that NumPy core knows
+ * nothing about this going on.
+ * The slight difficulty here is that `context` metadata needs to be mutated.
+ * It also adds a tiny bit of overhead, since we have to "fix" the descriptors
+ * and unpack the auxdata.
+ *
+ * This means that this currently needs to live within NumPy, as it needs both
+ * extensive API exposure to do it outside, as well as some thoughts on how to
+ * expose the `context` without breaking ABI forward compatibility.
+ * (I.e. we probably need to allocate the context and provide a copy function
+ * or so.)
+ */
+#define NPY_NO_DEPRECATED_API NPY_API_VERSION
+#define _MULTIARRAYMODULE
+#define _UMATHMODULE
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
+#include "numpy/ndarraytypes.h"
+
+#include "common.h"
+#include "array_method.h"
+#include "legacy_array_method.h"
+#include "dtypemeta.h"
+#include "dispatching.h"
+
+
+static NPY_CASTING
+wrapping_method_resolve_descriptors(
+ PyArrayMethodObject *self,
+ PyArray_DTypeMeta *dtypes[],
+ PyArray_Descr *given_descrs[],
+ PyArray_Descr *loop_descrs[],
+ npy_intp *view_offset)
+{
+ int nin = self->nin, nout = self->nout, nargs = nin + nout;
+ PyArray_Descr *orig_given_descrs[NPY_MAXARGS];
+ PyArray_Descr *orig_loop_descrs[NPY_MAXARGS];
+
+ if (self->translate_given_descrs(
+ nin, nout, self->wrapped_dtypes,
+ given_descrs, orig_given_descrs) < 0) {
+ return -1;
+ }
+ NPY_CASTING casting = self->wrapped_meth->resolve_descriptors(
+ self->wrapped_meth, self->wrapped_dtypes,
+ orig_given_descrs, orig_loop_descrs, view_offset);
+ for (int i = 0; i < nargs; i++) {
+ Py_XDECREF(orig_given_descrs);
+ }
+ if (casting < 0) {
+ return -1;
+ }
+ int res = self->translate_loop_descrs(
+ nin, nout, dtypes, given_descrs, orig_loop_descrs, loop_descrs);
+ for (int i = 0; i < nargs; i++) {
+ Py_DECREF(orig_given_descrs);
+ }
+ if (res < 0) {
+ return -1;
+ }
+ return casting;
+}
+
+
+typedef struct {
+ NpyAuxData base;
+ /* Note that if context is expanded this may become trickier: */
+ PyArrayMethod_Context orig_context;
+ PyArrayMethod_StridedLoop *orig_loop;
+ NpyAuxData *orig_auxdata;
+ PyArray_Descr *descriptors[NPY_MAXARGS];
+} wrapping_auxdata;
+
+
+#define WRAPPING_AUXDATA_FREELIST_SIZE 5
+static int wrapping_auxdata_freenum = 0;
+static wrapping_auxdata *wrapping_auxdata_freelist[WRAPPING_AUXDATA_FREELIST_SIZE] = {NULL};
+
+
+static void
+wrapping_auxdata_free(wrapping_auxdata *wrapping_auxdata)
+{
+ /* Free auxdata, everything else is borrowed: */
+ NPY_AUXDATA_FREE(wrapping_auxdata->orig_auxdata);
+ wrapping_auxdata->orig_auxdata = NULL;
+
+ if (wrapping_auxdata_freenum < WRAPPING_AUXDATA_FREELIST_SIZE) {
+ wrapping_auxdata_freelist[wrapping_auxdata_freenum] = wrapping_auxdata;
+ }
+ else {
+ PyMem_Free(wrapping_auxdata);
+ }
+}
+
+
+static wrapping_auxdata *
+get_wrapping_auxdata(void)
+{
+ wrapping_auxdata *res;
+ if (wrapping_auxdata_freenum > 0) {
+ wrapping_auxdata_freenum--;
+ res = wrapping_auxdata_freelist[wrapping_auxdata_freenum];
+ }
+ else {
+ res = PyMem_Calloc(1, sizeof(wrapping_auxdata));
+ if (res < 0) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ res->base.free = (void *)wrapping_auxdata_free;
+ res->orig_context.descriptors = res->descriptors;
+ }
+
+ return res;
+}
+
+
+static int
+wrapping_method_strided_loop(PyArrayMethod_Context *NPY_UNUSED(context),
+ char *const data[], npy_intp const dimensions[],
+ npy_intp const strides[], wrapping_auxdata *auxdata)
+{
+ /*
+ * If more things get stored on the context, it could be possible that
+ * we would have to copy it here. But currently, we do not.
+ */
+ return auxdata->orig_loop(
+ &auxdata->orig_context, data, dimensions, strides,
+ auxdata->orig_auxdata);
+}
+
+
+static int
+wrapping_method_get_loop(
+ PyArrayMethod_Context *context,
+ int aligned, int move_references, const npy_intp *strides,
+ PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
+ NPY_ARRAYMETHOD_FLAGS *flags)
+{
+ assert(move_references == 0); /* only used internally for "decref" funcs */
+ int nin = context->method->nin, nout = context->method->nout;
+
+ wrapping_auxdata *auxdata = get_wrapping_auxdata();
+ if (auxdata == NULL) {
+ return -1;
+ }
+
+ auxdata->orig_context.method = context->method->wrapped_meth;
+ auxdata->orig_context.caller = context->caller;
+
+ if (context->method->translate_given_descrs(
+ nin, nout, context->method->wrapped_dtypes,
+ context->descriptors, auxdata->orig_context.descriptors) < 0) {
+ NPY_AUXDATA_FREE((NpyAuxData *)auxdata);
+ return -1;
+ }
+ if (context->method->wrapped_meth->get_strided_loop(
+ &auxdata->orig_context, aligned, 0, strides,
+ &auxdata->orig_loop, &auxdata->orig_auxdata,
+ flags) < 0) {
+ NPY_AUXDATA_FREE((NpyAuxData *)auxdata);
+ return -1;
+ }
+
+ *out_loop = (PyArrayMethod_StridedLoop *)&wrapping_method_strided_loop;
+ *out_transferdata = (NpyAuxData *)auxdata;
+ return 0;
+}
+
+
+/**
+ * Allows creating of a fairly lightweight wrapper around an existing ufunc
+ * loop. The idea is mainly for units, as this is currently slightly limited
+ * in that it enforces that you cannot use a loop from another ufunc.
+ *
+ * @param ufunc_obj
+ * @param new_dtypes
+ * @param wrapped_dtypes
+ * @param translate_given_descrs See typedef comment
+ * @param translate_loop_descrs See typedef comment
+ * @return 0 on success -1 on failure
+ */
+NPY_NO_EXPORT int
+PyUFunc_AddWrappingLoop(PyObject *ufunc_obj,
+ PyArray_DTypeMeta *new_dtypes[], PyArray_DTypeMeta *wrapped_dtypes[],
+ translate_given_descrs_func *translate_given_descrs,
+ translate_loop_descrs_func *translate_loop_descrs)
+{
+ int res = -1;
+ PyUFuncObject *ufunc = (PyUFuncObject *)ufunc_obj;
+ PyObject *wrapped_dt_tuple = NULL;
+ PyObject *new_dt_tuple = NULL;
+ PyArrayMethodObject *meth = NULL;
+
+ if (!PyObject_TypeCheck(ufunc_obj, &PyUFunc_Type)) {
+ PyErr_SetString(PyExc_TypeError,
+ "ufunc object passed is not a ufunc!");
+ return -1;
+ }
+
+ wrapped_dt_tuple = PyArray_TupleFromItems(
+ ufunc->nargs, (PyObject **)wrapped_dtypes, 1);
+ if (wrapped_dt_tuple == NULL) {
+ goto finish;
+ }
+
+ PyArrayMethodObject *wrapped_meth = NULL;
+ PyObject *loops = ufunc->_loops;
+ Py_ssize_t length = PyList_Size(loops);
+ for (Py_ssize_t i = 0; i < length; i++) {
+ PyObject *item = PyList_GetItem(loops, i);
+ PyObject *cur_DType_tuple = PyTuple_GetItem(item, 0);
+ int cmp = PyObject_RichCompareBool(cur_DType_tuple, wrapped_dt_tuple, Py_EQ);
+ if (cmp < 0) {
+ goto finish;
+ }
+ if (cmp == 0) {
+ continue;
+ }
+ wrapped_meth = (PyArrayMethodObject *)PyTuple_GET_ITEM(item, 1);
+ if (!PyObject_TypeCheck(wrapped_meth, &PyArrayMethod_Type)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Matching loop was not an ArrayMethod.");
+ goto finish;
+ }
+ break;
+ }
+ if (wrapped_meth == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Did not find the to-be-wrapped loop in the ufunc.");
+ goto finish;
+ }
+
+ PyType_Slot slots[] = {
+ {NPY_METH_resolve_descriptors, &wrapping_method_resolve_descriptors},
+ {NPY_METH_get_loop, &wrapping_method_get_loop},
+ {0, NULL}
+ };
+
+ PyArrayMethod_Spec spec = {
+ .name = "wrapped-method",
+ .nin = wrapped_meth->nin,
+ .nout = wrapped_meth->nout,
+ .casting = wrapped_meth->casting,
+ .flags = wrapped_meth->flags,
+ .dtypes = new_dtypes,
+ .slots = slots,
+ };
+ PyBoundArrayMethodObject *bmeth = PyArrayMethod_FromSpec_int(&spec, 1);
+ if (bmeth == NULL) {
+ goto finish;
+ }
+
+ Py_INCREF(bmeth->method);
+ meth = bmeth->method;
+ Py_SETREF(bmeth, NULL);
+
+ /* Finalize the "wrapped" part of the new ArrayMethod */
+ meth->wrapped_dtypes = PyMem_Malloc(ufunc->nargs * sizeof(PyArray_DTypeMeta *));
+ if (meth->wrapped_dtypes == NULL) {
+ goto finish;
+ }
+
+ Py_INCREF(wrapped_meth);
+ meth->wrapped_meth = wrapped_meth;
+ meth->translate_given_descrs = translate_given_descrs;
+ meth->translate_loop_descrs = translate_loop_descrs;
+ for (int i = 0; i < ufunc->nargs; i++) {
+ Py_XINCREF(wrapped_dtypes[i]);
+ meth->wrapped_dtypes[i] = wrapped_dtypes[i];
+ }
+
+ new_dt_tuple = PyArray_TupleFromItems(
+ ufunc->nargs, (PyObject **)new_dtypes, 1);
+ if (new_dt_tuple == NULL) {
+ goto finish;
+ }
+
+ PyObject *info = PyTuple_Pack(2, new_dt_tuple, meth);
+ if (info == NULL) {
+ goto finish;
+ }
+
+ res = PyUFunc_AddLoop(ufunc, info, 0);
+ Py_DECREF(info);
+
+ finish:
+ Py_XDECREF(wrapped_dt_tuple);
+ Py_XDECREF(new_dt_tuple);
+ Py_XDECREF(meth);
+ return res;
+}