8000 MAINT: Rewrite can-cast logic in terms of NEP 42 by seberg · Pull Request #17401 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

MAINT: Rewrite can-cast logic in terms of NEP 42 #17401

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 25, 2020
Merged
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
3 changes: 2 additions & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@ stages:
CC: /usr/bin/clang
condition: eq(variables['USE_OPENBLAS'], '1')
- script: python setup.py build -j 4 build_ext --inplace install
displayName: 'Build NumPy without OpenBLAS'
displayName: 'Build NumPy without OpenBLAS and new casting'
env:
NPY_USE_NEW_CASTINGIMPL: 1
BLAS: None
LAPACK: None
ATLAS: None
Expand Down
4 changes: 2 additions & 2 deletions doc/neps/nep-0042-new-dtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ Its ``resolve_descriptors`` function may look like::
# This is always an "unsafe" cast, but for int64, we can represent
# it by a simple view (if the dtypes are both canonical).
# (represented as C-side flags here).
safety_and_view = NPY_UNSAFE_CASTING | NPY_CAST_IS_VIEW
safety_and_view = NPY_UNSAFE_CASTING | _NPY_CAST_IS_VIEW
return safety_and_view, (from_dtype, to_dtype)

.. note::
Expand Down Expand Up @@ -1305,7 +1305,7 @@ The external API for ``CastingImpl`` will be limited initially to defining:
``casting`` will be set to ``NPY_EQUIV_CASTING``, ``NPY_SAFE_CASTING``,
``NPY_UNSAFE_CASTING``, or ``NPY_SAME_KIND_CASTING``.
A new, additional flag,
``NPY_CAST_IS_VIEW``, can be set to indicate that no cast is necessary and a
``_NPY_CAST_IS_VIEW``, can be set to indicate that no cast is necessary and a
view is sufficient to perform the cast. The cast should return
``-1`` when a custom error is set and ``NPY_NO_CASTING`` to indicate
that a generic casting error should be set (this is in most cases
Expand Down
15 changes: 15 additions & 0 deletions doc/source/reference/global_state.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,18 @@ in C which iterates through arrays that may or may not be
contiguous in memory.
Most users will have no reason to change these; for details
see the :ref:`memory layout <memory-layout>` documentation.

Using the new casting implementation
------------------------------------

Within NumPy 1.20 it is possible to enable the new experimental casting
implementation for testing purposes. To do this set::

NPY_USE_NEW_CASTINGIMPL=1

Setting the flag is only useful to aid with NumPy developement to ensure the
new version is bug free and should be avoided for production code.
It is a helpful test for projects that either create custom datatypes or
use for example complicated structured dtypes. The flag is expected to be
removed in 1.21 with the new version being always in use.

1 change: 1 addition & 0 deletions numpy/core/code_generators/genapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
join('multiarray', 'array_assign_array.c'),
join('multiarray', 'array_assign_scalar.c'),
join('multiarray', 'array_coercion.c'),
join('multiarray', 'array_method.c'),
join('multiarray', 'arrayobject.c'),
join('multiarray', 'arraytypes.c.src'),
join('multiarray', 'buffer.c'),
Expand Down
16 changes: 15 additions & 1 deletion numpy/core/include/numpy/ndarraytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ typedef enum {

/* For specifying allowed casting in operations which support it */
typedef enum {
_NPY_ERROR_OCCURRED_IN_CAST = -1,
/* Only allow identical types */
NPY_NO_CASTING=0,
/* Allow identical and byte swapped types */
Expand All @@ -219,7 +220,14 @@ typedef enum {
/* Allow safe casts or casts within the same kind */
NPY_SAME_KIND_CASTING=3,
/* Allow any casts */
NPY_UNSAFE_CASTING=4
NPY_UNSAFE_CASTING=4,
/*
* Flag to allow signalling that a cast is a view, this flag is not
* valid when requesting a cast of specific safety.
* _NPY_CAST_IS_VIEW|NPY_EQUIV_CASTING means the same as NPY_NO_CASTING.
*/
// TODO-DTYPES: Needs to be documented.
_NPY_CAST_IS_VIEW = 1 << 16,
} NPY_CASTING;

typedef enum {
Expand Down Expand Up @@ -1900,6 +1908,12 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size,
default_descr_function *default_descr;
common_dtype_function *common_dtype;
common_instance_function *common_instance;
/*
* Dictionary of ArrayMethods representing most possible casts
* (structured and object are exceptions).
* This should potentially become a weak mapping in the future.
*/
PyObject *castingimpls;
};

#endif /* NPY_INTERNAL_BUILD */
Expand Down
13 changes: 13 additions & 0 deletions numpy/core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
NPY_RELAXED_STRIDES_DEBUG = (os.environ.get('NPY_RELAXED_STRIDES_DEBUG', "0") != "0")
NPY_RELAXED_STRIDES_DEBUG = NPY_RELAXED_STRIDES_DEBUG and NPY_RELAXED_STRIDES_CHECKING

# Set to True to use the new casting implementation as much as implemented.
# Allows running the full test suit to exercise the new machinery until
# it is used as default and the old version is eventually deleted.
NPY_USE_NEW_CASTINGIMPL = os.environ.get('NPY_USE_NEW_CASTINGIMPL', "0") != "0"

# XXX: ugly, we use a class to avoid calling twice some expensive functions in
# config.h/numpyconfig.h. I don't see a better way because distutils force
# config.h generation inside an Extension class, and as such sharing
Expand Down Expand Up @@ -468,6 +473,10 @@ def generate_config_h(ext, build_dir):
if NPY_RELAXED_STRIDES_DEBUG:
moredefs.append(('NPY_RELAXED_STRIDES_DEBUG', 1))

# Use the new experimental casting implementation in NumPy 1.20:
if NPY_USE_NEW_CASTINGIMPL:
moredefs.append(('NPY_USE_NEW_CASTINGIMPL', 1))

# Get long double representation
rep = check_long_double_representation(config_cmd)
moredefs.append(('HAVE_LDOUBLE_%s' % rep, 1))
Expand Down Expand Up @@ -769,6 +778,7 @@ def get_mathlib_info(*args):
join('src', 'multiarray', 'arraytypes.h'),
join('src', 'multiarray', 'arrayfunction_override.h'),
join('src', 'multiarray', 'array_coercion.h'),
join('src', 'multiarray', 'array_method.h'),
join('src', 'multiarray', 'npy_buffer.h'),
join('src', 'multiarray', 'calculation.h'),
join('src', 'multiarray', 'common.h'),
Expand All @@ -784,6 +794,7 @@ def get_mathlib_info(*args):
join('src', 'multiarray', 'getset.h'),
join('src', 'multiarray', 'hashdescr.h'),
join('src', 'multiarray', 'iterators.h'),
join('src', 'multiarray', 'legacy_dtype_implementation.h'),
join('src', 'multiarray', 'mapping.h'),
join('src', 'multiarray', 'methods.h'),
join('src', 'multiarray', 'multiarraymodule.h'),
Expand Down Expand Up @@ -824,6 +835,7 @@ def get_mathlib_info(*args):
join('src', 'multiarray', 'arrayobject.c'),
join('src', 'multiarray', 'arraytypes.c.src'),
join('src', 'multiarray', 'array_coercion.c'),
join('src', 'multiarray', 'array_method.c'),
join('src', 'multiarray', 'array_assign_scalar.c'),
join('src', 'multiarray', 'array_assign_array.c'),
join('src', 'multiarray', 'arrayfunction_override.c'),
Expand All @@ -850,6 +862,7 @@ def get_mathlib_info(*args):
join('src', 'multiarray', 'hashdescr.c'),
join('src', 'multiarray', 'item_selection.c'),
join('src', 'multiarray', 'iterators.c'),
join('src', 'multiarray', 'legacy_dtype_implementation.c'),
join('src', 'multiarray', 'lowlevel_strided_loops.c.src'),
join('src', 'multiarray', 'mapping.c'),
join('src', 'multiarray', 'methods.c'),
Expand Down
3 changes: 3 additions & 0 deletions numpy/core/src/multiarray/_datetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,4 +373,7 @@ datetime_arange(PyObject *start, PyObject *stop, PyObject *step,
NPY_NO_EXPORT PyArray_Descr *
find_object_datetime_type(PyObject *obj, int type_num);

NPY_NO_EXPORT int
PyArray_InitializeDatetimeCasts();

#endif
97 changes: 96 additions & 1 deletion numpy/core/src/multiarray/_multiarray_tests.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "common.h"
#include "mem_overlap.h"
#include "npy_extint128.h"
#include "array_method.h"

#if defined(MS_WIN32) || defined(__CYGWIN__)
#define EXPORT(x) __declspec(dllexport) x
Expand Down Expand Up @@ -977,6 +978,79 @@ get_c_wrapping_array(PyObject* NPY_UNUSED(self), PyObject* arg)
}


static PyObject *
get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
{
PyObject *result = PyList_New(0);
if (result == NULL) {
return NULL;
}
PyObject *classes = PyObject_CallMethod(
(PyObject *)&PyArrayDescr_Type, "__subclasses__", "");
if (classes == NULL) {
return NULL;
}
Py_SETREF(classes, PySequence_Fast(classes, NULL));
if (classes == NULL) {
goto fail;
}

Py_ssize_t nclass = PySequence_Length(classes);
for (Py_ssize_t i = 0; i < nclass; i++) {
PyArray_DTypeMeta *from_dtype = (
(PyArray_DTypeMeta *)PySequence_Fast_GET_ITEM(classes, i));
if (from_dtype->abstract) {
/*
* TODO: In principle probably needs to recursively check this,
* also we may allow casts to abstract dtypes at some point.
*/
continue;
}

PyObject *to_dtype, *cast_obj;
Py_ssize_t pos = 0;

while (PyDict_Next(from_dtype->castingimpls, &pos, &to_dtype, &cast_obj)) {
if (cast_obj == Py_None) {
continue;
}
PyArrayMethodObject *cast = (PyArrayMethodObject *)cast_obj;

/* Pass some information about this cast out! */
PyObject *cast_info = Py_BuildValue("{sOsOsisisisisisssi}",
"from", from_dtype,
"to", to_dtype,
"legacy", (cast->name != NULL &&
strncmp(cast->name, "legacy_", 7) == 0),
"casting", cast->casting & ~_NPY_CAST_IS_VIEW,
"requires_pyapi", cast->flags & NPY_METH_REQUIRES_PYAPI,
"supports_unaligned",
cast->flags & NPY_METH_SUPPORTS_UNALIGNED,
"no_floatingpoint_errors",
cast->flags & NPY_METH_NO_FLOATINGPOINT_ERRORS,
"name", cast->name,
"cast_is_view",
cast->casting & _NPY_CAST_IS_VIEW);
if (cast_info == NULL) {
goto fail;
}
int res = PyList_Append(result, cast_info);
Py_DECREF(cast_info);
if (res < 0) {
goto fail;
}
}
}
Py_DECREF(classes);
return result;

fail:
Py_XDECREF(classes);
Py_XDECREF(result);
return NULL;
}


/*
* Test C-api level item getting.
*/
Expand Down Expand Up @@ -2010,6 +2084,18 @@ getset_numericops(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
return ret;
}


static PyObject *
uses_new_casts(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
{
#if NPY_USE_NEW_CASTINGIMPL
Py_RETURN_TRUE;
#else
Py_RETURN_FALSE;
#endif
}


static PyObject *
run_byteorder_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
Expand Down Expand Up @@ -2113,8 +2199,8 @@ run_casting_converter(PyObject* NPY_UNUSED(self), PyObject *args)
case NPY_SAFE_CASTING: return PyUnicode_FromString("NPY_SAFE_CASTING");
case NPY_SAME_KIND_CASTING: return PyUnicode_FromString("NPY_SAME_KIND_CASTING");
case NPY_UNSAFE_CASTING: return PyUnicode_FromString("NPY_UNSAFE_CASTING");
default: return PyLong_FromLong(casting);
}
return PyLong_FromLong(casting);
}

static PyObject *
Expand Down Expand Up @@ -2194,6 +2280,12 @@ static PyMethodDef Multiarray_TestsMethods[] = {
{"get_c_wrapping_array",
get_c_wrapping_array,
METH_O, NULL},
{"get_all_cast_information",
get_all_cast_information,
METH_NOARGS,
"Return a list with info on all available casts. Some of the info"
"may differ for an actual cast if it uses value-based casting "
"(flexible types)."},
{"array_indexing",
array_indexing,
METH_VARARGS, NULL},
Expand Down Expand Up @@ -2254,6 +2346,9 @@ static PyMethodDef Multiarray_TestsMethods[] = {
{"getset_numericops",
getset_numericops,
METH_NOARGS, NULL},
{"uses_new_casts",
uses_new_casts,
METH_NOARGS, NULL},
/**begin repeat
* #name = cabs, carg#
*/
Expand Down
Loading
0