summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2021-12-20 13:27:40 -0600
committerGitHub <noreply@github.com>2021-12-20 13:27:40 -0600
commit229c1757609efbbeea96b90746bcbe5ba41e0397 (patch)
tree24af9054f4e543709195f9e298e51cd9c03dac03 /numpy
parent1531820cec59939a34745c869d1365a85fc6869c (diff)
downloadnumpy-229c1757609efbbeea96b90746bcbe5ba41e0397.tar.gz
MAINT: Use intp output param viewable casts/methods (#20176)
This removes the cast-is-view flag with a more generic intp output. Such an output could (theoretically) be nonzero, indicating e.g. the offset of a complex number into a real one. (We do not use this yet, though!) The only "tricky" part is that the MinCastSafety helper used to deal with the view-offset as well, and now we have to deal with it explicitly when e.g. multiple fields have to be checked. Bumps the experimental-dtype-api number, since the signatures changed. * MAINT,DOC: Cleanups and clarfications based on Marten's comments Co-Authored-By: Marten van Kerkwijk <mhvk@astro.utoronto.ca> * MAINT: Rename `PyArray_GetCastSafety` to `PyArray_GetCastInfo` Better captures the fact that it also returns the view-offset information now. * MAINT: Fix structured cast-is-view logic * MAINT: Address review comments by Marten Co-authored-by: Marten van Kerkwijk <mhvk@astro.utoronto.ca>
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/include/numpy/experimental_dtype_api.h22
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h7
-rw-r--r--numpy/core/src/multiarray/_multiarray_tests.c.src8
-rw-r--r--numpy/core/src/multiarray/array_method.c60
-rw-r--r--numpy/core/src/multiarray/array_method.h3
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c312
-rw-r--r--numpy/core/src/multiarray/convert_datatype.h11
-rw-r--r--numpy/core/src/multiarray/datetime.c33
-rw-r--r--numpy/core/src/multiarray/dtype_transfer.c26
-rw-r--r--numpy/core/src/multiarray/experimental_public_dtype_api.c2
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c3
-rw-r--r--numpy/core/src/umath/_scaled_float_dtype.c21
-rw-r--r--numpy/core/src/umath/legacy_array_method.c6
-rw-r--r--numpy/core/src/umath/legacy_array_method.h2
-rw-r--r--numpy/core/src/umath/ufunc_object.c12
-rw-r--r--numpy/core/tests/test_casting_unittests.py180
-rwxr-xr-xnumpy/testing/print_coercion_tables.py11
17 files changed, 509 insertions, 210 deletions
diff --git a/numpy/core/include/numpy/experimental_dtype_api.h b/numpy/core/include/numpy/experimental_dtype_api.h
index effa66baf..83639f186 100644
--- a/numpy/core/include/numpy/experimental_dtype_api.h
+++ b/numpy/core/include/numpy/experimental_dtype_api.h
@@ -82,6 +82,15 @@
* The new DType API is designed in a way to make it potentially useful for
* alternative "array-like" implementations. This will require careful
* exposure of details and functions and is not part of this experimental API.
+ *
+ * Brief (incompatibility) changelog
+ * =================================
+ *
+ * 2. None (only additions).
+ * 3. New `npy_intp *view_offset` argument for `resolve_descriptors`.
+ * This replaces the `NPY_CAST_IS_VIEW` flag. It can be set to 0 if the
+ * operation is a view, and is pre-initialized to `NPY_MIN_INTP` indicating
+ * that the operation is not a view.
*/
#ifndef NUMPY_CORE_INCLUDE_NUMPY_EXPERIMENTAL_DTYPE_API_H_
@@ -206,16 +215,6 @@ typedef int _ufunc_addpromoter_func(
#define PyUFunc_AddPromoter \
(*(_ufunc_addpromoter_func *)(__experimental_dtype_api_table[1]))
-/*
- * In addition to the normal casting levels, NPY_CAST_IS_VIEW indicates
- * that no cast operation is necessary at all (although a copy usually will be)
- *
- * NOTE: The most likely modification here is to add an additional
- * `view_offset` output to resolve_descriptors. If set, it would
- * indicate both that it is a view and what offset to use. This means that
- * e.g. `arr.imag` could be implemented by an ArrayMethod.
- */
-#define NPY_CAST_IS_VIEW _NPY_CAST_IS_VIEW
/*
* The resolve descriptors function, must be able to handle NULL values for
@@ -236,7 +235,8 @@ typedef NPY_CASTING (resolve_descriptors_function)(
/* Input descriptors (instances). Outputs may be NULL. */
PyArray_Descr **given_descrs,
/* Exact loop descriptors to use, must not hold references on error */
- PyArray_Descr **loop_descrs);
+ PyArray_Descr **loop_descrs,
+ npy_intp *view_offset);
/* NOT public yet: Signature needs adapting as external API. */
#define _NPY_METH_get_loop 2
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index 0bd259983..47d063178 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -221,13 +221,6 @@ typedef enum {
NPY_SAME_KIND_CASTING=3,
/* Allow any casts */
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 {
diff --git a/numpy/core/src/multiarray/_multiarray_tests.c.src b/numpy/core/src/multiarray/_multiarray_tests.c.src
index 4c8f241f7..36937629d 100644
--- a/numpy/core/src/multiarray/_multiarray_tests.c.src
+++ b/numpy/core/src/multiarray/_multiarray_tests.c.src
@@ -1067,20 +1067,18 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
PyArrayMethodObject *cast = (PyArrayMethodObject *)cast_obj;
/* Pass some information about this cast out! */
- PyObject *cast_info = Py_BuildValue("{sOsOsisisisisisssi}",
+ PyObject *cast_info = Py_BuildValue("{sOsOsisisisisiss}",
"from", from_dtype,
"to", to_dtype,
"legacy", (cast->name != NULL &&
strncmp(cast->name, "legacy_", 7) == 0),
- "casting", cast->casting & ~_NPY_CAST_IS_VIEW,
+ "casting", cast->casting,
"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);
+ "name", cast->name);
if (cast_info == NULL) {
goto fail;
}
diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c
index d93dac506..b421d9e4f 100644
--- a/numpy/core/src/multiarray/array_method.c
+++ b/numpy/core/src/multiarray/array_method.c
@@ -48,13 +48,19 @@
*
* We could allow setting the output descriptors specifically to simplify
* this step.
+ *
+ * Note that the default version will indicate that the cast can be done
+ * as using `arr.view(new_dtype)` if the default cast-safety is
+ * set to "no-cast". This default function cannot be used if a view may
+ * be sufficient for casting but the cast is not always "no-cast".
*/
static NPY_CASTING
default_resolve_descriptors(
PyArrayMethodObject *method,
PyArray_DTypeMeta **dtypes,
PyArray_Descr **input_descrs,
- PyArray_Descr **output_descrs)
+ PyArray_Descr **output_descrs,
+ npy_intp *view_offset)
{
int nin = method->nin;
int nout = method->nout;
@@ -76,6 +82,13 @@ default_resolve_descriptors(
* abstract ones or unspecified outputs). We can use the common-dtype
* operation to provide a default here.
*/
+ if (method->casting == NPY_NO_CASTING) {
+ /*
+ * By (current) definition no-casting should imply viewable. This
+ * is currently indicated for example for object to object cast.
+ */
+ *view_offset = 0;
+ }
return method->casting;
fail:
@@ -102,9 +115,10 @@ is_contiguous(
/**
* The default method to fetch the correct loop for a cast or ufunc
* (at the time of writing only casts).
- * The default version can return loops explicitly registered during method
- * creation. It does specialize contiguous loops, although has to check
- * all descriptors itemsizes for this.
+ * Note that the default function provided here will only indicate that a cast
+ * can be done as a view (i.e., arr.view(new_dtype)) when this is trivially
+ * true, i.e., for cast safety "no-cast". It will not recognize view as an
+ * option for other casts (e.g., viewing '>i8' as '>i4' with an offset of 4).
*
* @param context
* @param aligned
@@ -166,7 +180,7 @@ validate_spec(PyArrayMethod_Spec *spec)
"not exceed %d. (method: %s)", NPY_MAXARGS, spec->name);
return -1;
}
- switch (spec->casting & ~_NPY_CAST_IS_VIEW) {
+ switch (spec->casting) {
case NPY_NO_CASTING:
case NPY_EQUIV_CASTING:
case NPY_SAFE_CASTING:
@@ -495,8 +509,9 @@ boundarraymethod_dealloc(PyObject *self)
/*
- * Calls resolve_descriptors() and returns the casting level and the resolved
- * descriptors as a tuple. If the operation is impossible returns (-1, None).
+ * Calls resolve_descriptors() and returns the casting level, the resolved
+ * descriptors as a tuple, and a possible view-offset (integer or None).
+ * If the operation is impossible returns (-1, None, None).
* May raise an error, but usually should not.
* The function validates the casting attribute compared to the returned
* casting level.
@@ -551,14 +566,15 @@ boundarraymethod__resolve_descripors(
}
}
+ npy_intp view_offset = NPY_MIN_INTP;
NPY_CASTING casting = self->method->resolve_descriptors(
- self->method, self->dtypes, given_descrs, loop_descrs);
+ self->method, self->dtypes, given_descrs, loop_descrs, &view_offset);
if (casting < 0 && PyErr_Occurred()) {
return NULL;
}
else if (casting < 0) {
- return Py_BuildValue("iO", casting, Py_None);
+ return Py_BuildValue("iO", casting, Py_None, Py_None);
}
PyObject *result_tuple = PyTuple_New(nin + nout);
@@ -570,9 +586,22 @@ boundarraymethod__resolve_descripors(
PyTuple_SET_ITEM(result_tuple, i, (PyObject *)loop_descrs[i]);
}
+ PyObject *view_offset_obj;
+ if (view_offset == NPY_MIN_INTP) {
+ Py_INCREF(Py_None);
+ view_offset_obj = Py_None;
+ }
+ else {
+ view_offset_obj = PyLong_FromSsize_t(view_offset);
+ if (view_offset_obj == NULL) {
+ Py_DECREF(result_tuple);
+ return NULL;
+ }
+ }
+
/*
- * The casting flags should be the most generic casting level (except the
- * cast-is-view flag. If no input is parametric, it must match exactly.
+ * The casting flags should be the most generic casting level.
+ * If no input is parametric, it must match exactly.
*
* (Note that these checks are only debugging checks.)
*/
@@ -584,7 +613,7 @@ boundarraymethod__resolve_descripors(
}
}
if (self->method->casting != -1) {
- NPY_CASTING cast = casting & ~_NPY_CAST_IS_VIEW;
+ NPY_CASTING cast = casting;
if (self->method->casting !=
PyArray_MinCastSafety(cast, self->method->casting)) {
PyErr_Format(PyExc_RuntimeError,
@@ -592,6 +621,7 @@ boundarraymethod__resolve_descripors(
"(set level is %d, got %d for method %s)",
self->method->casting, cast, self->method->name);
Py_DECREF(result_tuple);
+ Py_DECREF(view_offset_obj);
return NULL;
}
if (!parametric) {
@@ -608,12 +638,13 @@ boundarraymethod__resolve_descripors(
"(set level is %d, got %d for method %s)",
self->method->casting, cast, self->method->name);
Py_DECREF(result_tuple);
+ Py_DECREF(view_offset_obj);
return NULL;
}
}
}
- return Py_BuildValue("iN", casting, result_tuple);
+ return Py_BuildValue("iNN", casting, result_tuple, view_offset_obj);
}
@@ -694,8 +725,9 @@ boundarraymethod__simple_strided_call(
return NULL;
}
+ npy_intp view_offset = NPY_MIN_INTP;
NPY_CASTING casting = self->method->resolve_descriptors(
- self->method, self->dtypes, descrs, out_descrs);
+ self->method, self->dtypes, descrs, out_descrs, &view_offset);
if (casting < 0) {
PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL;
diff --git a/numpy/core/src/multiarray/array_method.h b/numpy/core/src/multiarray/array_method.h
index 7b7372bd0..35b9033e0 100644
--- a/numpy/core/src/multiarray/array_method.h
+++ b/numpy/core/src/multiarray/array_method.h
@@ -70,7 +70,8 @@ typedef NPY_CASTING (resolve_descriptors_function)(
struct PyArrayMethodObject_tag *method,
PyArray_DTypeMeta **dtypes,
PyArray_Descr **given_descrs,
- PyArray_Descr **loop_descrs);
+ PyArray_Descr **loop_descrs,
+ npy_intp *view_offset);
typedef int (get_loop_function)(
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 3135d6989..b21fc3cfa 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -223,14 +223,11 @@ PyArray_MinCastSafety(NPY_CASTING casting1, NPY_CASTING casting2)
if (casting1 < 0 || casting2 < 0) {
return -1;
}
- NPY_CASTING view = casting1 & casting2 & _NPY_CAST_IS_VIEW;
- casting1 = casting1 & ~_NPY_CAST_IS_VIEW;
- casting2 = casting2 & ~_NPY_CAST_IS_VIEW;
/* larger casting values are less safe */
if (casting1 > casting2) {
- return casting1 | view;
+ return casting1;
}
- return casting2 | view;
+ return casting2;
}
@@ -363,29 +360,41 @@ PyArray_CastAnyTo(PyArrayObject *out, PyArrayObject *mp)
static NPY_CASTING
_get_cast_safety_from_castingimpl(PyArrayMethodObject *castingimpl,
- PyArray_DTypeMeta *dtypes[2], PyArray_Descr *from, PyArray_Descr *to)
+ PyArray_DTypeMeta *dtypes[2], PyArray_Descr *from, PyArray_Descr *to,
+ npy_intp *view_offset)
{
PyArray_Descr *descrs[2] = {from, to};
PyArray_Descr *out_descrs[2];
+ *view_offset = NPY_MIN_INTP;
NPY_CASTING casting = castingimpl->resolve_descriptors(
- castingimpl, dtypes, descrs, out_descrs);
+ castingimpl, dtypes, descrs, out_descrs, view_offset);
if (casting < 0) {
return -1;
}
/* The returned descriptors may not match, requiring a second check */
if (out_descrs[0] != descrs[0]) {
- NPY_CASTING from_casting = PyArray_GetCastSafety(
- descrs[0], out_descrs[0], NULL);
+ npy_intp from_offset = NPY_MIN_INTP;
+ NPY_CASTING from_casting = PyArray_GetCastInfo(
+ descrs[0], out_descrs[0], NULL, &from_offset);
casting = PyArray_MinCastSafety(casting, from_casting);
+ if (from_offset != *view_offset) {
+ /* `view_offset` differs: The multi-step cast cannot be a view. */
+ *view_offset = NPY_MIN_INTP;
+ }
if (casting < 0) {
goto finish;
}
}
if (descrs[1] != NULL && out_descrs[1] != descrs[1]) {
- NPY_CASTING from_casting = PyArray_GetCastSafety(
- descrs[1], out_descrs[1], NULL);
+ npy_intp from_offset = NPY_MIN_INTP;
+ NPY_CASTING from_casting = PyArray_GetCastInfo(
+ descrs[1], out_descrs[1], NULL, &from_offset);
casting = PyArray_MinCastSafety(casting, from_casting);
+ if (from_offset != *view_offset) {
+ /* `view_offset` differs: The multi-step cast cannot be a view. */
+ *view_offset = NPY_MIN_INTP;
+ }
if (casting < 0) {
goto finish;
}
@@ -396,15 +405,21 @@ _get_cast_safety_from_castingimpl(PyArrayMethodObject *castingimpl,
Py_DECREF(out_descrs[1]);
/*
* Check for less harmful non-standard returns. The following two returns
- * should never happen. They would be roughly equivalent, but less precise,
- * versions of `(NPY_NO_CASTING|_NPY_CAST_IS_VIEW)`.
- * 1. No-casting must imply cast-is-view.
- * 2. Equivalent-casting + cast-is-view is (currently) the definition
- * of a "no" cast (there may be reasons to relax this).
- * Note that e.g. `(NPY_UNSAFE_CASTING|_NPY_CAST_IS_VIEW)` is valid.
+ * should never happen:
+ * 1. No-casting must imply a view offset of 0.
+ * 2. Equivalent-casting + 0 view offset is (usually) the definition
+ * of a "no" cast. However, changing the order of fields can also
+ * create descriptors that are not equivalent but views.
+ * Note that unsafe casts can have a view offset. For example, in
+ * principle, casting `<i8` to `<i4` is a cast with 0 offset.
*/
- assert(casting != NPY_NO_CASTING);
- assert(casting != (NPY_EQUIV_CASTING|_NPY_CAST_IS_VIEW));
+ if (*view_offset != 0) {
+ assert(casting != NPY_NO_CASTING);
+ }
+ else {
+ assert(casting != NPY_EQUIV_CASTING
+ || (PyDataType_HASFIELDS(from) && PyDataType_HASFIELDS(to)));
+ }
return casting;
}
@@ -420,11 +435,13 @@ _get_cast_safety_from_castingimpl(PyArrayMethodObject *castingimpl,
* @param to The descriptor to cast to (may be NULL)
* @param to_dtype If `to` is NULL, must pass the to_dtype (otherwise this
* is ignored).
+ * @param[out] view_offset
* @return NPY_CASTING or -1 on error or if the cast is not possible.
*/
NPY_NO_EXPORT NPY_CASTING
-PyArray_GetCastSafety(
- PyArray_Descr *from, PyArray_Descr *to, PyArray_DTypeMeta *to_dtype)
+PyArray_GetCastInfo(
+ PyArray_Descr *from, PyArray_Descr *to, PyArray_DTypeMeta *to_dtype,
+ npy_intp *view_offset)
{
if (to != NULL) {
to_dtype = NPY_DTYPE(to);
@@ -441,7 +458,7 @@ PyArray_GetCastSafety(
PyArrayMethodObject *castingimpl = (PyArrayMethodObject *)meth;
PyArray_DTypeMeta *dtypes[2] = {NPY_DTYPE(from), to_dtype};
NPY_CASTING casting = _get_cast_safety_from_castingimpl(castingimpl,
- dtypes, from, to);
+ dtypes, from, to, view_offset);
Py_DECREF(meth);
return casting;
@@ -449,8 +466,8 @@ PyArray_GetCastSafety(
/**
- * Check whether a cast is safe, see also `PyArray_GetCastSafety` for
- * a similar function. Unlike GetCastSafety, this function checks the
+ * Check whether a cast is safe, see also `PyArray_GetCastInfo` for
+ * a similar function. Unlike GetCastInfo, this function checks the
* `castingimpl->casting` when available. This allows for two things:
*
* 1. It avoids calling `resolve_descriptors` in some cases.
@@ -493,8 +510,9 @@ PyArray_CheckCastSafety(NPY_CASTING casting,
}
PyArray_DTypeMeta *dtypes[2] = {NPY_DTYPE(from), to_dtype};
+ npy_intp view_offset;
NPY_CASTING safety = _get_cast_safety_from_castingimpl(castingimpl,
- dtypes, from, to);
+ dtypes, from, to, &view_offset);
Py_DECREF(meth);
/* If casting is the smaller (or equal) safety we match */
if (safety < 0) {
@@ -971,8 +989,9 @@ PyArray_CastDescrToDType(PyArray_Descr *descr, PyArray_DTypeMeta *given_DType)
PyArray_Descr *loop_descrs[2];
PyArrayMethodObject *meth = (PyArrayMethodObject *)tmp;
+ npy_intp view_offset = NPY_MIN_INTP;
NPY_CASTING casting = meth->resolve_descriptors(
- meth, dtypes, given_descrs, loop_descrs);
+ meth, dtypes, given_descrs, loop_descrs, &view_offset);
Py_DECREF(tmp);
if (casting < 0) {
goto error;
@@ -2289,7 +2308,8 @@ legacy_same_dtype_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
Py_INCREF(given_descrs[0]);
loop_descrs[0] = given_descrs[0];
@@ -2315,7 +2335,8 @@ legacy_same_dtype_resolve_descriptors(
*/
if (PyDataType_ISNOTSWAPPED(loop_descrs[0]) ==
PyDataType_ISNOTSWAPPED(loop_descrs[1])) {
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ *view_offset = 0;
+ return NPY_NO_CASTING;
}
return NPY_EQUIV_CASTING;
}
@@ -2354,7 +2375,8 @@ simple_cast_resolve_descriptors(
PyArrayMethodObject *self,
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
assert(NPY_DT_is_legacy(dtypes[0]) && NPY_DT_is_legacy(dtypes[1]));
@@ -2378,7 +2400,8 @@ simple_cast_resolve_descriptors(
}
if (PyDataType_ISNOTSWAPPED(loop_descrs[0]) ==
PyDataType_ISNOTSWAPPED(loop_descrs[1])) {
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ *view_offset = 0;
+ return NPY_NO_CASTING;
}
return NPY_EQUIV_CASTING;
}
@@ -2572,7 +2595,8 @@ cast_to_string_resolve_descriptors(
PyArrayMethodObject *self,
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *NPY_UNUSED(view_offset))
{
/*
* NOTE: The following code used to be part of PyArray_AdaptFlexibleDType
@@ -2723,7 +2747,8 @@ string_to_string_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
Py_INCREF(given_descrs[0]);
loop_descrs[0] = given_descrs[0];
@@ -2739,19 +2764,29 @@ string_to_string_resolve_descriptors(
loop_descrs[1] = given_descrs[1];
}
- if (loop_descrs[0]->elsize == loop_descrs[1]->elsize) {
- if (PyDataType_ISNOTSWAPPED(loop_descrs[0]) ==
- PyDataType_ISNOTSWAPPED(loop_descrs[1])) {
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ if (loop_descrs[0]->elsize < loop_descrs[1]->elsize) {
+ /* New string is longer: safe but cannot be a view */
+ return NPY_SAFE_CASTING;
+ }
+ else {
+ /* New string fits into old: if the byte-order matches can be a view */
+ int not_swapped = (PyDataType_ISNOTSWAPPED(loop_descrs[0])
+ == PyDataType_ISNOTSWAPPED(loop_descrs[1]));
+ if (not_swapped) {
+ *view_offset = 0;
+ }
+
+ if (loop_descrs[0]->elsize > loop_descrs[1]->elsize) {
+ return NPY_SAME_KIND_CASTING;
+ }
+ /* The strings have the same length: */
+ if (not_swapped) {
+ return NPY_NO_CASTING;
}
else {
return NPY_EQUIV_CASTING;
}
}
- else if (loop_descrs[0]->elsize <= loop_descrs[1]->elsize) {
- return NPY_SAFE_CASTING;
- }
- return NPY_SAME_KIND_CASTING;
}
@@ -2866,7 +2901,8 @@ PyArray_InitializeStringCasts(void)
*/
static NPY_CASTING
cast_to_void_dtype_class(
- PyArray_Descr **given_descrs, PyArray_Descr **loop_descrs)
+ PyArray_Descr **given_descrs, PyArray_Descr **loop_descrs,
+ npy_intp *view_offset)
{
/* `dtype="V"` means unstructured currently (compare final path) */
loop_descrs[1] = PyArray_DescrNewFromType(NPY_VOID);
@@ -2876,11 +2912,13 @@ cast_to_void_dtype_class(
loop_descrs[1]->elsize = given_descrs[0]->elsize;
Py_INCREF(given_descrs[0]);
loop_descrs[0] = given_descrs[0];
+
+ *view_offset = 0;
if (loop_descrs[0]->type_num == NPY_VOID &&
loop_descrs[0]->subarray == NULL && loop_descrs[1]->names == NULL) {
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ return NPY_NO_CASTING;
}
- return NPY_SAFE_CASTING | _NPY_CAST_IS_VIEW;
+ return NPY_SAFE_CASTING;
}
@@ -2889,12 +2927,13 @@ nonstructured_to_structured_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
NPY_CASTING casting;
if (given_descrs[1] == NULL) {
- return cast_to_void_dtype_class(given_descrs, loop_descrs);
+ return cast_to_void_dtype_class(given_descrs, loop_descrs, view_offset);
}
if (given_descrs[1]->subarray != NULL) {
@@ -2903,12 +2942,18 @@ nonstructured_to_structured_resolve_descriptors(
* possible to allow a view if the field has exactly one element.
*/
casting = NPY_SAFE_CASTING;
+ npy_intp sub_view_offset = NPY_MIN_INTP;
/* Subarray dtype */
- NPY_CASTING base_casting = PyArray_GetCastSafety(
- given_descrs[0], given_descrs[1]->subarray->base, NULL);
+ NPY_CASTING base_casting = PyArray_GetCastInfo(
+ given_descrs[0], given_descrs[1]->subarray->base, NULL,
+ &sub_view_offset);
if (base_casting < 0) {
return -1;
}
+ if (given_descrs[1]->elsize == given_descrs[1]->subarray->base->elsize) {
+ /* A single field, view is OK if sub-view is */
+ *view_offset = sub_view_offset;
+ }
casting = PyArray_MinCastSafety(casting, base_casting);
}
else if (given_descrs[1]->names != NULL) {
@@ -2920,21 +2965,32 @@ nonstructured_to_structured_resolve_descriptors(
else {
/* Considered at most unsafe casting (but this could be changed) */
casting = NPY_UNSAFE_CASTING;
- if (PyTuple_Size(given_descrs[1]->names) == 1) {
- /* A view may be acceptable */
- casting |= _NPY_CAST_IS_VIEW;
- }
Py_ssize_t pos = 0;
PyObject *key, *tuple;
while (PyDict_Next(given_descrs[1]->fields, &pos, &key, &tuple)) {
PyArray_Descr *field_descr = (PyArray_Descr *)PyTuple_GET_ITEM(tuple, 0);
- NPY_CASTING field_casting = PyArray_GetCastSafety(
- given_descrs[0], field_descr, NULL);
+ npy_intp field_view_off = NPY_MIN_INTP;
+ NPY_CASTING field_casting = PyArray_GetCastInfo(
+ given_descrs[0], field_descr, NULL, &field_view_off);
casting = PyArray_MinCastSafety(casting, field_casting);
if (casting < 0) {
return -1;
}
+ if (field_view_off != NPY_MIN_INTP) {
+ npy_intp to_off = PyLong_AsSsize_t(PyTuple_GET_ITEM(tuple, 1));
+ if (error_converting(to_off)) {
+ return -1;
+ }
+ *view_offset = field_view_off - to_off;
+ }
+ }
+ if (PyTuple_Size(given_descrs[1]->names) != 1) {
+ /*
+ * Assume that a view is impossible when there is more than one
+ * field. (Fields could overlap, but that seems weird...)
+ */
+ *view_offset = NPY_MIN_INTP;
}
}
}
@@ -2944,15 +3000,20 @@ nonstructured_to_structured_resolve_descriptors(
!PyDataType_REFCHK(given_descrs[0])) {
/*
* A simple view, at the moment considered "safe" (the refcheck is
- * probably not necessary, but more future proof
+ * probably not necessary, but more future proof)
*/
- casting = NPY_SAFE_CASTING | _NPY_CAST_IS_VIEW;
+ *view_offset = 0;
+ casting = NPY_SAFE_CASTING;
}
else if (given_descrs[0]->elsize <= given_descrs[1]->elsize) {
casting = NPY_SAFE_CASTING;
}
else {
casting = NPY_UNSAFE_CASTING;
+ /* new elsize is smaller so a view is OK (reject refs for now) */
+ if (!PyDataType_REFCHK(given_descrs[0])) {
+ *view_offset = 0;
+ }
}
}
@@ -3048,6 +3109,8 @@ PyArray_GetGenericToVoidCastingImpl(void)
method->casting = -1;
method->resolve_descriptors = &nonstructured_to_structured_resolve_descriptors;
method->get_strided_loop = &nonstructured_to_structured_get_loop;
+ method->nin = 1;
+ method->nout = 1;
return (PyObject *)method;
}
@@ -3058,12 +3121,19 @@ structured_to_nonstructured_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
PyArray_Descr *base_descr;
+ /* The structured part may allow a view (and have its own offset): */
+ npy_intp struct_view_offset = NPY_MIN_INTP;
if (given_descrs[0]->subarray != NULL) {
base_descr = given_descrs[0]->subarray->base;
+ /* A view is possible if the subarray has exactly one element: */
+ if (given_descrs[0]->elsize == given_descrs[0]->subarray->base->elsize) {
+ struct_view_offset = 0;
+ }
}
else if (given_descrs[0]->names != NULL) {
if (PyTuple_Size(given_descrs[0]->names) != 1) {
@@ -3073,6 +3143,10 @@ structured_to_nonstructured_resolve_descriptors(
PyObject *key = PyTuple_GetItem(given_descrs[0]->names, 0);
PyObject *base_tup = PyDict_GetItem(given_descrs[0]->fields, key);
base_descr = (PyArray_Descr *)PyTuple_GET_ITEM(base_tup, 0);
+ struct_view_offset = PyLong_AsSsize_t(PyTuple_GET_ITEM(base_tup, 1));
+ if (error_converting(struct_view_offset)) {
+ return -1;
+ }
}
else {
/*
@@ -3080,20 +3154,29 @@ structured_to_nonstructured_resolve_descriptors(
* at this time they go back to legacy behaviour using getitem/setitem.
*/
base_descr = NULL;
+ struct_view_offset = 0;
}
/*
- * The cast is always considered unsafe, so the PyArray_GetCastSafety
- * result currently does not matter.
+ * The cast is always considered unsafe, so the PyArray_GetCastInfo
+ * result currently only matters for the view_offset.
*/
- if (base_descr != NULL && PyArray_GetCastSafety(
- base_descr, given_descrs[1], dtypes[1]) < 0) {
+ npy_intp base_view_offset = NPY_MIN_INTP;
+ if (base_descr != NULL && PyArray_GetCastInfo(
+ base_descr, given_descrs[1], dtypes[1], &base_view_offset) < 0) {
return -1;
}
+ if (base_view_offset != NPY_MIN_INTP
+ && struct_view_offset != NPY_MIN_INTP) {
+ *view_offset = base_view_offset + struct_view_offset;
+ }
/* Void dtypes always do the full cast. */
if (given_descrs[1] == NULL) {
loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
+ if (loop_descrs[1] == NULL) {
+ return -1;
+ }
/*
* Special case strings here, it should be useless (and only actually
* work for empty arrays). Possibly this should simply raise for
@@ -3187,6 +3270,8 @@ PyArray_GetVoidToGenericCastingImpl(void)
method->casting = -1;
method->resolve_descriptors = &structured_to_nonstructured_resolve_descriptors;
method->get_strided_loop = &structured_to_nonstructured_get_loop;
+ method->nin = 1;
+ method->nout = 1;
return (PyObject *)method;
}
@@ -3201,16 +3286,19 @@ PyArray_GetVoidToGenericCastingImpl(void)
* implementations on the dtype, to avoid duplicate work.
*/
static NPY_CASTING
-can_cast_fields_safety(PyArray_Descr *from, PyArray_Descr *to)
+can_cast_fields_safety(
+ PyArray_Descr *from, PyArray_Descr *to, npy_intp *view_offset)
{
- NPY_CASTING casting = NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
-
Py_ssize_t field_count = PyTuple_Size(from->names);
if (field_count != PyTuple_Size(to->names)) {
/* TODO: This should be rejected! */
return NPY_UNSAFE_CASTING;
}
+
+ NPY_CASTING casting = NPY_NO_CASTING;
+ *view_offset = 0; /* if there are no fields, a view is OK. */
for (Py_ssize_t i = 0; i < field_count; i++) {
+ npy_intp field_view_off = NPY_MIN_INTP;
PyObject *from_key = PyTuple_GET_ITEM(from->names, i);
PyObject *from_tup = PyDict_GetItemWithError(from->fields, from_key);
if (from_tup == NULL) {
@@ -3229,15 +3317,40 @@ can_cast_fields_safety(PyArray_Descr *from, PyArray_Descr *to)
}
PyArray_Descr *to_base = (PyArray_Descr*)PyTuple_GET_ITEM(to_tup, 0);
- NPY_CASTING field_casting = PyArray_GetCastSafety(from_base, to_base, NULL);
+ NPY_CASTING field_casting = PyArray_GetCastInfo(
+ from_base, to_base, NULL, &field_view_off);
if (field_casting < 0) {
return -1;
}
casting = PyArray_MinCastSafety(casting, field_casting);
+
+ /* Adjust the "view offset" by the field offsets: */
+ if (field_view_off != NPY_MIN_INTP) {
+ npy_intp to_off = PyLong_AsSsize_t(PyTuple_GET_ITEM(to_tup, 1));
+ if (error_converting(to_off)) {
+ return -1;
+ }
+ npy_intp from_off = PyLong_AsSsize_t(PyTuple_GET_ITEM(from_tup, 1));
+ if (error_converting(from_off)) {
+ return -1;
+ }
+ field_view_off = field_view_off - to_off + from_off;
+ }
+
+ /*
+ * If there is one field, use its field offset. After that propagate
+ * the view offset if they match and set to "invalid" if not.
+ */
+ if (i == 0) {
+ *view_offset = field_view_off;
+ }
+ else if (*view_offset != field_view_off) {
+ *view_offset = NPY_MIN_INTP;
+ }
}
- if (!(casting & _NPY_CAST_IS_VIEW)) {
- assert((casting & ~_NPY_CAST_IS_VIEW) != NPY_NO_CASTING);
- return casting;
+ if (*view_offset != 0) {
+ /* If the calculated `view_offset` is not 0, it can only be "equiv" */
+ return PyArray_MinCastSafety(casting, NPY_EQUIV_CASTING);
}
/*
@@ -3277,38 +3390,42 @@ void_to_void_resolve_descriptors(
PyArrayMethodObject *self,
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
NPY_CASTING casting;
if (given_descrs[1] == NULL) {
/* This is weird, since it doesn't return the original descr, but... */
- return cast_to_void_dtype_class(given_descrs, loop_descrs);
+ return cast_to_void_dtype_class(given_descrs, loop_descrs, view_offset);
}
if (given_descrs[0]->names != NULL && given_descrs[1]->names != NULL) {
/* From structured to structured, need to check fields */
- casting = can_cast_fields_safety(given_descrs[0], given_descrs[1]);
+ casting = can_cast_fields_safety(
+ given_descrs[0], given_descrs[1], view_offset);
}
else if (given_descrs[0]->names != NULL) {
return structured_to_nonstructured_resolve_descriptors(
- self, dtypes, given_descrs, loop_descrs);
+ self, dtypes, given_descrs, loop_descrs, view_offset);
}
else if (given_descrs[1]->names != NULL) {
return nonstructured_to_structured_resolve_descriptors(
- self, dtypes, given_descrs, loop_descrs);
+ self, dtypes, given_descrs, loop_descrs, view_offset);
}
else if (given_descrs[0]->subarray == NULL &&
given_descrs[1]->subarray == NULL) {
/* Both are plain void dtypes */
if (given_descrs[0]->elsize == given_descrs[1]->elsize) {
- casting = NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ casting = NPY_NO_CASTING;
+ *view_offset = 0;
}
else if (given_descrs[0]->elsize < given_descrs[1]->elsize) {
casting = NPY_SAFE_CASTING;
}
else {
casting = NPY_SAME_KIND_CASTING;
+ *view_offset = 0;
}
}
else {
@@ -3322,20 +3439,51 @@ void_to_void_resolve_descriptors(
/* If the shapes do not match, this is at most an unsafe cast */
casting = NPY_UNSAFE_CASTING;
+ /*
+ * We can use a view in two cases:
+ * 1. The shapes and elsizes matches, so any view offset applies to
+ * each element of the subarray identically.
+ * (in practice this probably implies the `view_offset` will be 0)
+ * 2. There is exactly one element and the subarray has no effect
+ * (can be tested by checking if the itemsizes of the base matches)
+ */
+ npy_bool subarray_layout_supports_view = NPY_FALSE;
if (from_sub && to_sub) {
int res = PyObject_RichCompareBool(from_sub->shape, to_sub->shape, Py_EQ);
if (res < 0) {
return -1;
}
else if (res) {
- /* Both are subarrays and the shape matches */
- casting = NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ /* Both are subarrays and the shape matches, could be no cast */
+ casting = NPY_NO_CASTING;
+ /* May be a view if there is one element or elsizes match */
+ if (from_sub->base->elsize == to_sub->base->elsize
+ || given_descrs[0]->elsize == from_sub->base->elsize) {
+ subarray_layout_supports_view = NPY_TRUE;
+ }
+ }
+ }
+ else if (from_sub) {
+ /* May use a view if "from" has only a single element: */
+ if (given_descrs[0]->elsize == from_sub->base->elsize) {
+ subarray_layout_supports_view = NPY_TRUE;
+ }
+ }
+ else {
+ /* May use a view if "from" has only a single element: */
+ if (given_descrs[1]->elsize == to_sub->base->elsize) {
+ subarray_layout_supports_view = NPY_TRUE;
}
}
PyArray_Descr *from_base = (from_sub == NULL) ? given_descrs[0] : from_sub->base;
PyArray_Descr *to_base = (to_sub == NULL) ? given_descrs[1] : to_sub->base;
- NPY_CASTING field_casting = PyArray_GetCastSafety(from_base, to_base, NULL);
+ /* An offset for */
+ NPY_CASTING field_casting = PyArray_GetCastInfo(
+ from_base, to_base, NULL, view_offset);
+ if (!subarray_layout_supports_view) {
+ *view_offset = NPY_MIN_INTP;
+ }
if (field_casting < 0) {
return -1;
}
@@ -3443,7 +3591,8 @@ object_to_any_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *NPY_UNUSED(view_offset))
{
if (given_descrs[1] == NULL) {
/*
@@ -3513,7 +3662,8 @@ any_to_object_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *NPY_UNUSED(view_offset))
{
if (given_descrs[1] == NULL) {
loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
@@ -3591,10 +3741,6 @@ object_to_object_get_loop(
static int
PyArray_InitializeObjectToObjectCast(void)
{
- /*
- * The object dtype does not support byte order changes, so its cast
- * is always a direct view.
- */
PyArray_DTypeMeta *Object = PyArray_DTypeFromTypeNum(NPY_OBJECT);
PyArray_DTypeMeta *dtypes[2] = {Object, Object};
PyType_Slot slots[] = {
@@ -3602,7 +3748,7 @@ PyArray_InitializeObjectToObjectCast(void)
{0, NULL}};
PyArrayMethod_Spec spec = {
.name = "object_to_object_cast",
- .casting = NPY_NO_CASTING | _NPY_CAST_IS_VIEW,
+ .casting = NPY_NO_CASTING,
.nin = 1,
.nout = 1,
.flags = NPY_METH_REQUIRES_PYAPI | NPY_METH_SUPPORTS_UNALIGNED,
diff --git a/numpy/core/src/multiarray/convert_datatype.h b/numpy/core/src/multiarray/convert_datatype.h
index 5e0682f22..6b4413959 100644
--- a/numpy/core/src/multiarray/convert_datatype.h
+++ b/numpy/core/src/multiarray/convert_datatype.h
@@ -68,8 +68,9 @@ NPY_NO_EXPORT NPY_CASTING
PyArray_MinCastSafety(NPY_CASTING casting1, NPY_CASTING casting2);
NPY_NO_EXPORT NPY_CASTING
-PyArray_GetCastSafety(
- PyArray_Descr *from, PyArray_Descr *to, PyArray_DTypeMeta *to_dtype);
+PyArray_GetCastInfo(
+ PyArray_Descr *from, PyArray_Descr *to, PyArray_DTypeMeta *to_dtype,
+ npy_intp *view_offset);
NPY_NO_EXPORT int
PyArray_CheckCastSafety(NPY_CASTING casting,
@@ -80,7 +81,8 @@ legacy_same_dtype_resolve_descriptors(
PyArrayMethodObject *self,
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2]);
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset);
NPY_NO_EXPORT int
legacy_cast_get_strided_loop(
@@ -94,7 +96,8 @@ simple_cast_resolve_descriptors(
PyArrayMethodObject *self,
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *input_descrs[2],
- PyArray_Descr *loop_descrs[2]);
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset);
NPY_NO_EXPORT int
PyArray_InitializeCasts(void);
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index e0064c017..03ebaa7ce 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -3746,8 +3746,6 @@ find_object_datetime_type(PyObject *obj, int type_num)
}
-
-
/*
* Describes casting within datetimes or timedelta
*/
@@ -3756,7 +3754,8 @@ time_to_time_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
/* This is a within-dtype cast, which currently must handle byteswapping */
Py_INCREF(given_descrs[0]);
@@ -3772,14 +3771,14 @@ time_to_time_resolve_descriptors(
int is_timedelta = given_descrs[0]->type_num == NPY_TIMEDELTA;
if (given_descrs[0] == given_descrs[1]) {
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ *view_offset = 0;
+ return NPY_NO_CASTING;
}
- NPY_CASTING byteorder_may_allow_view = 0;
- if (PyDataType_ISNOTSWAPPED(loop_descrs[0]) ==
- PyDataType_ISNOTSWAPPED(loop_descrs[1])) {
- byteorder_may_allow_view = _NPY_CAST_IS_VIEW;
- }
+ npy_bool byteorder_may_allow_view = (
+ PyDataType_ISNOTSWAPPED(loop_descrs[0])
+ == PyDataType_ISNOTSWAPPED(loop_descrs[1]));
+
PyArray_DatetimeMetaData *meta1, *meta2;
meta1 = get_datetime_metadata_from_dtype(loop_descrs[0]);
assert(meta1 != NULL);
@@ -3798,12 +3797,16 @@ time_to_time_resolve_descriptors(
((meta2->base >= 7) && (meta1->base - meta2->base == 3)
&& ((meta1->num / meta2->num) == 1000000000))) {
if (byteorder_may_allow_view) {
- return NPY_NO_CASTING | byteorder_may_allow_view;
+ *view_offset = 0;
+ return NPY_NO_CASTING;
}
return NPY_EQUIV_CASTING;
}
else if (meta1->base == NPY_FR_GENERIC) {
- return NPY_SAFE_CASTING | byteorder_may_allow_view;
+ if (byteorder_may_allow_view) {
+ *view_offset = 0;
+ }
+ return NPY_SAFE_CASTING ;
}
else if (meta2->base == NPY_FR_GENERIC) {
/* TODO: This is actually an invalid cast (casting will error) */
@@ -3931,10 +3934,11 @@ datetime_to_timedelta_resolve_descriptors(
/* In the current setup both strings and unicode casts support all outputs */
static NPY_CASTING
time_to_string_resolve_descriptors(
- PyArrayMethodObject *self,
+ PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr **given_descrs,
- PyArray_Descr **loop_descrs)
+ PyArray_Descr **loop_descrs,
+ npy_intp *NPY_UNUSED(view_offset))
{
if (given_descrs[1] != NULL && dtypes[0]->type_num == NPY_DATETIME) {
/*
@@ -4013,7 +4017,8 @@ string_to_datetime_cast_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *NPY_UNUSED(view_offset))
{
if (given_descrs[1] == NULL) {
/* NOTE: This doesn't actually work, and will error during the cast */
diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c
index 8fb44c4f6..4877f8dfa 100644
--- a/numpy/core/src/multiarray/dtype_transfer.c
+++ b/numpy/core/src/multiarray/dtype_transfer.c
@@ -2991,7 +2991,8 @@ _strided_to_strided_multistep_cast(
* transferfunction and transferdata.
*/
static NPY_INLINE int
-init_cast_info(NPY_cast_info *cast_info, NPY_CASTING *casting,
+init_cast_info(
+ NPY_cast_info *cast_info, NPY_CASTING *casting, npy_intp *view_offset,
PyArray_Descr *src_dtype, PyArray_Descr *dst_dtype, int main_step)
{
PyObject *meth = PyArray_GetCastingImpl(
@@ -3016,7 +3017,8 @@ init_cast_info(NPY_cast_info *cast_info, NPY_CASTING *casting,
PyArray_Descr *in_descr[2] = {src_dtype, dst_dtype};
*casting = cast_info->context.method->resolve_descriptors(
- cast_info->context.method, dtypes, in_descr, cast_info->descriptors);
+ cast_info->context.method, dtypes,
+ in_descr, cast_info->descriptors, view_offset);
if (NPY_UNLIKELY(*casting < 0)) {
if (!PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
@@ -3071,6 +3073,9 @@ _clear_cast_info_after_get_loop_failure(NPY_cast_info *cast_info)
* transfer function from the each casting implementation (ArrayMethod).
* May set the transfer function to NULL when the cast can be achieved using
* a view.
+ * TODO: Expand the view functionality for general offsets, not just 0:
+ * Partial casts could be skipped also for `view_offset != 0`.
+ *
* The `out_needs_api` flag must be initialized.
*
* NOTE: In theory casting errors here could be slightly misleading in case
@@ -3101,9 +3106,12 @@ define_cast_for_descrs(
castdata.main.func = NULL;
castdata.to.func = NULL;
castdata.from.func = NULL;
+ /* `view_offset` passed to `init_cast_info` but unused for the main cast */
+ npy_intp view_offset = NPY_MIN_INTP;
NPY_CASTING casting = -1;
- if (init_cast_info(cast_info, &casting, src_dtype, dst_dtype, 1) < 0) {
+ if (init_cast_info(
+ cast_info, &casting, &view_offset, src_dtype, dst_dtype, 1) < 0) {
return -1;
}
@@ -3123,17 +3131,18 @@ define_cast_for_descrs(
*/
if (NPY_UNLIKELY(src_dtype != cast_info->descriptors[0] || must_wrap)) {
NPY_CASTING from_casting = -1;
+ npy_intp from_view_offset = NPY_MIN_INTP;
/* Cast function may not support the input, wrap if necessary */
if (init_cast_info(
- &castdata.from, &from_casting,
+ &castdata.from, &from_casting, &from_view_offset,
src_dtype, cast_info->descriptors[0], 0) < 0) {
goto fail;
}
casting = PyArray_MinCastSafety(casting, from_casting);
/* Prepare the actual cast (if necessary): */
- if (from_casting & _NPY_CAST_IS_VIEW && !must_wrap) {
- /* This step is not necessary and can be skipped. */
+ if (from_view_offset == 0 && !must_wrap) {
+ /* This step is not necessary and can be skipped */
castdata.from.func = &_dec_src_ref_nop; /* avoid NULL */
NPY_cast_info_xfree(&castdata.from);
}
@@ -3161,16 +3170,17 @@ define_cast_for_descrs(
*/
if (NPY_UNLIKELY(dst_dtype != cast_info->descriptors[1] || must_wrap)) {
NPY_CASTING to_casting = -1;
+ npy_intp to_view_offset = NPY_MIN_INTP;
/* Cast function may not support the output, wrap if necessary */
if (init_cast_info(
- &castdata.to, &to_casting,
+ &castdata.to, &to_casting, &to_view_offset,
cast_info->descriptors[1], dst_dtype, 0) < 0) {
goto fail;
}
casting = PyArray_MinCastSafety(casting, to_casting);
/* Prepare the actual cast (if necessary): */
- if (to_casting & _NPY_CAST_IS_VIEW && !must_wrap) {
+ if (to_view_offset == 0 && !must_wrap) {
/* This step is not necessary and can be skipped. */
castdata.to.func = &_dec_src_ref_nop; /* avoid NULL */
NPY_cast_info_xfree(&castdata.to);
diff --git a/numpy/core/src/multiarray/experimental_public_dtype_api.c b/numpy/core/src/multiarray/experimental_public_dtype_api.c
index 4b9c7199b..e9d191002 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 2
+#define EXPERIMENTAL_DTYPE_API_VERSION 3
typedef struct{
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index d1627d639..789446d0c 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -1500,7 +1500,8 @@ PyArray_EquivTypes(PyArray_Descr *type1, PyArray_Descr *type2)
* Do not use PyArray_CanCastTypeTo because it supports legacy flexible
* dtypes as input.
*/
- NPY_CASTING safety = PyArray_GetCastSafety(type1, type2, NULL);
+ npy_intp view_offset;
+ NPY_CASTING safety = PyArray_GetCastInfo(type1, type2, NULL, &view_offset);
if (safety < 0) {
PyErr_Clear();
return 0;
diff --git a/numpy/core/src/umath/_scaled_float_dtype.c b/numpy/core/src/umath/_scaled_float_dtype.c
index b6c19362a..a214b32aa 100644
--- a/numpy/core/src/umath/_scaled_float_dtype.c
+++ b/numpy/core/src/umath/_scaled_float_dtype.c
@@ -325,7 +325,8 @@ sfloat_to_sfloat_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
loop_descrs[0] = given_descrs[0];
Py_INCREF(loop_descrs[0]);
@@ -341,7 +342,8 @@ sfloat_to_sfloat_resolve_descriptors(
if (((PyArray_SFloatDescr *)loop_descrs[0])->scaling
== ((PyArray_SFloatDescr *)loop_descrs[1])->scaling) {
/* same scaling is just a view */
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ *view_offset = 0;
+ return NPY_NO_CASTING;
}
else if (-((PyArray_SFloatDescr *)loop_descrs[0])->scaling
== ((PyArray_SFloatDescr *)loop_descrs[1])->scaling) {
@@ -384,7 +386,8 @@ float_to_from_sfloat_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *dtypes[2],
PyArray_Descr *NPY_UNUSED(given_descrs[2]),
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *view_offset)
{
loop_descrs[0] = NPY_DT_CALL_default_descr(dtypes[0]);
if (loop_descrs[0] == NULL) {
@@ -394,7 +397,8 @@ float_to_from_sfloat_resolve_descriptors(
if (loop_descrs[1] == NULL) {
return -1;
}
- return NPY_NO_CASTING | _NPY_CAST_IS_VIEW;
+ *view_offset = 0;
+ return NPY_NO_CASTING;
}
@@ -422,7 +426,8 @@ sfloat_to_bool_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
PyArray_Descr *given_descrs[2],
- PyArray_Descr *loop_descrs[2])
+ PyArray_Descr *loop_descrs[2],
+ npy_intp *NPY_UNUSED(view_offset))
{
Py_INCREF(given_descrs[0]);
loop_descrs[0] = given_descrs[0];
@@ -541,7 +546,8 @@ multiply_sfloats_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[3]),
PyArray_Descr *given_descrs[3],
- PyArray_Descr *loop_descrs[3])
+ PyArray_Descr *loop_descrs[3],
+ npy_intp *NPY_UNUSED(view_offset))
{
/*
* Multiply the scaling for the result. If the result was passed in we
@@ -602,7 +608,8 @@ add_sfloats_resolve_descriptors(
PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[3]),
PyArray_Descr *given_descrs[3],
- PyArray_Descr *loop_descrs[3])
+ PyArray_Descr *loop_descrs[3],
+ npy_intp *NPY_UNUSED(view_offset))
{
/*
* Here we accept an output descriptor (the inner loop can deal with it),
diff --git a/numpy/core/src/umath/legacy_array_method.c b/numpy/core/src/umath/legacy_array_method.c
index ef24edff1..f4b2aed96 100644
--- a/numpy/core/src/umath/legacy_array_method.c
+++ b/numpy/core/src/umath/legacy_array_method.c
@@ -103,7 +103,8 @@ NPY_NO_EXPORT NPY_CASTING
wrapped_legacy_resolve_descriptors(PyArrayMethodObject *NPY_UNUSED(self),
PyArray_DTypeMeta *NPY_UNUSED(dtypes[]),
PyArray_Descr *NPY_UNUSED(given_descrs[]),
- PyArray_Descr *NPY_UNUSED(loop_descrs[]))
+ PyArray_Descr *NPY_UNUSED(loop_descrs[]),
+ npy_intp *NPY_UNUSED(view_offset))
{
PyErr_SetString(PyExc_RuntimeError,
"cannot use legacy wrapping ArrayMethod without calling the ufunc "
@@ -121,7 +122,8 @@ simple_legacy_resolve_descriptors(
PyArrayMethodObject *method,
PyArray_DTypeMeta **dtypes,
PyArray_Descr **given_descrs,
- PyArray_Descr **output_descrs)
+ PyArray_Descr **output_descrs,
+ npy_intp *NPY_UNUSED(view_offset))
{
int i = 0;
int nin = method->nin;
diff --git a/numpy/core/src/umath/legacy_array_method.h b/numpy/core/src/umath/legacy_array_method.h
index 0dec1fb3a..d20b4fb08 100644
--- a/numpy/core/src/umath/legacy_array_method.h
+++ b/numpy/core/src/umath/legacy_array_method.h
@@ -27,7 +27,7 @@ get_wrapped_legacy_ufunc_loop(PyArrayMethod_Context *context,
NPY_NO_EXPORT NPY_CASTING
wrapped_legacy_resolve_descriptors(PyArrayMethodObject *,
- PyArray_DTypeMeta **, PyArray_Descr **, PyArray_Descr **);
+ PyArray_DTypeMeta **, PyArray_Descr **, PyArray_Descr **, npy_intp *);
#endif /*_NPY_LEGACY_ARRAY_METHOD_H */
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 78f6f4b5a..52b354353 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -1073,13 +1073,15 @@ check_for_trivial_loop(PyArrayMethodObject *ufuncimpl,
int must_copy = !PyArray_ISALIGNED(op[i]);
if (dtypes[i] != PyArray_DESCR(op[i])) {
- NPY_CASTING safety = PyArray_GetCastSafety(
- PyArray_DESCR(op[i]), dtypes[i], NULL);
+ npy_intp view_offset;
+ NPY_CASTING safety = PyArray_GetCastInfo(
+ PyArray_DESCR(op[i]), dtypes[i], NULL, &view_offset);
if (safety < 0 && PyErr_Occurred()) {
/* A proper error during a cast check, should be rare */
return -1;
}
- if (!(safety & _NPY_CAST_IS_VIEW)) {
+ if (view_offset != 0) {
+ /* NOTE: Could possibly implement non-zero view offsets */
must_copy = 1;
}
@@ -4508,8 +4510,10 @@ resolve_descriptors(int nop,
if (ufuncimpl->resolve_descriptors != &wrapped_legacy_resolve_descriptors) {
/* The default: use the `ufuncimpl` as nature intended it */
+ npy_intp view_offset = NPY_MIN_INTP; /* currently ignored */
+
NPY_CASTING safety = ufuncimpl->resolve_descriptors(ufuncimpl,
- signature, original_dtypes, dtypes);
+ signature, original_dtypes, dtypes, &view_offset);
if (safety < 0) {
goto finish;
}
diff --git a/numpy/core/tests/test_casting_unittests.py b/numpy/core/tests/test_casting_unittests.py
index cb4792090..a57e46fd0 100644
--- a/numpy/core/tests/test_casting_unittests.py
+++ b/numpy/core/tests/test_casting_unittests.py
@@ -76,7 +76,6 @@ class Casting(enum.IntEnum):
safe = 2
same_kind = 3
unsafe = 4
- cast_is_view = 1 << 16
def _get_cancast_table():
@@ -259,14 +258,14 @@ class TestCasting:
del default
for to_dt in [to_Dt(), to_Dt().newbyteorder()]:
- casting, (from_res, to_res) = cast._resolve_descriptors(
- (from_dt, to_dt))
+ casting, (from_res, to_res), view_off = (
+ cast._resolve_descriptors((from_dt, to_dt)))
assert(type(from_res) == from_Dt)
assert(type(to_res) == to_Dt)
- if casting & Casting.cast_is_view:
+ if view_off is not None:
# If a view is acceptable, this is "no" casting
# and byte order must be matching.
- assert casting == Casting.no | Casting.cast_is_view
+ assert casting == Casting.no
# The above table lists this as "equivalent"
assert Casting.equiv == CAST_TABLE[from_Dt][to_Dt]
# Note that to_res may not be the same as from_dt
@@ -299,7 +298,7 @@ class TestCasting:
to_dt = to_dt.values[0]
cast = get_castingimpl(type(from_dt), type(to_dt))
- casting, (from_res, to_res) = cast._resolve_descriptors(
+ casting, (from_res, to_res), view_off = cast._resolve_descriptors(
(from_dt, to_dt))
if from_res is not from_dt or to_res is not to_dt:
@@ -307,7 +306,7 @@ class TestCasting:
# each of which should is tested individually.
return
- safe = (casting & ~Casting.cast_is_view) <= Casting.safe
+ safe = casting <= Casting.safe
del from_res, to_res, casting
arr1, arr2, values = self.get_data(from_dt, to_dt)
@@ -355,14 +354,15 @@ class TestCasting:
for time_dt in time_dtypes:
cast = get_castingimpl(type(from_dt), type(time_dt))
- casting, (from_res, to_res) = cast._resolve_descriptors(
+ casting, (from_res, to_res), view_off = cast._resolve_descriptors(
(from_dt, time_dt))
assert from_res is from_dt
assert to_res is time_dt
del from_res, to_res
- assert(casting & CAST_TABLE[from_Dt][type(time_dt)])
+ assert casting & CAST_TABLE[from_Dt][type(time_dt)]
+ assert view_off is None
int64_dt = np.dtype(np.int64)
arr1, arr2, values = self.get_data(from_dt, int64_dt)
@@ -391,31 +391,37 @@ class TestCasting:
assert arr2_o.tobytes() == arr2.tobytes()
@pytest.mark.parametrize(
- ["from_dt", "to_dt", "expected_casting", "nom", "denom"],
- [("M8[ns]", None,
- Casting.no | Casting.cast_is_view, 1, 1),
- (str(np.dtype("M8[ns]").newbyteorder()), None, Casting.equiv, 1, 1),
- ("M8", "M8[ms]", Casting.safe | Casting.cast_is_view, 1, 1),
- ("M8[ms]", "M8", Casting.unsafe, 1, 1), # should be invalid cast
- ("M8[5ms]", "M8[5ms]", Casting.no | Casting.cast_is_view, 1, 1),
- ("M8[ns]", "M8[ms]", Casting.same_kind, 1, 10**6),
- ("M8[ms]", "M8[ns]", Casting.safe, 10**6, 1),
- ("M8[ms]", "M8[7ms]", Casting.same_kind, 1, 7),
- ("M8[4D]", "M8[1M]", Casting.same_kind, None,
+ ["from_dt", "to_dt", "expected_casting", "expected_view_off",
+ "nom", "denom"],
+ [("M8[ns]", None, Casting.no, 0, 1, 1),
+ (str(np.dtype("M8[ns]").newbyteorder()), None,
+ Casting.equiv, None, 1, 1),
+ ("M8", "M8[ms]", Casting.safe, 0, 1, 1),
+ # should be invalid cast:
+ ("M8[ms]", "M8", Casting.unsafe, None, 1, 1),
+ ("M8[5ms]", "M8[5ms]", Casting.no, 0, 1, 1),
+ ("M8[ns]", "M8[ms]", Casting.same_kind, None, 1, 10**6),
+ ("M8[ms]", "M8[ns]", Casting.safe, None, 10**6, 1),
+ ("M8[ms]", "M8[7ms]", Casting.same_kind, None, 1, 7),
+ ("M8[4D]", "M8[1M]", Casting.same_kind, None, None,
# give full values based on NumPy 1.19.x
[-2**63, 0, -1, 1314, -1315, 564442610]),
- ("m8[ns]", None, Casting.no | Casting.cast_is_view, 1, 1),
- (str(np.dtype("m8[ns]").newbyteorder()), None, Casting.equiv, 1, 1),
- ("m8", "m8[ms]", Casting.safe | Casting.cast_is_view, 1, 1),
- ("m8[ms]", "m8", Casting.unsafe, 1, 1), # should be invalid cast
- ("m8[5ms]", "m8[5ms]", Casting.no | Casting.cast_is_view, 1, 1),
- ("m8[ns]", "m8[ms]", Casting.same_kind, 1, 10**6),
- ("m8[ms]", "m8[ns]", Casting.safe, 10**6, 1),
- ("m8[ms]", "m8[7ms]", Casting.same_kind, 1, 7),
- ("m8[4D]", "m8[1M]", Casting.unsafe, None,
+ ("m8[ns]", None, Casting.no, 0, 1, 1),
+ (str(np.dtype("m8[ns]").newbyteorder()), None,
+ Casting.equiv, None, 1, 1),
+ ("m8", "m8[ms]", Casting.safe, 0, 1, 1),
+ # should be invalid cast:
+ ("m8[ms]", "m8", Casting.unsafe, None, 1, 1),
+ ("m8[5ms]", "m8[5ms]", Casting.no, 0, 1, 1),
+ ("m8[ns]", "m8[ms]", Casting.same_kind, None, 1, 10**6),
+ ("m8[ms]", "m8[ns]", Casting.safe, None, 10**6, 1),
+ ("m8[ms]", "m8[7ms]", Casting.same_kind, None, 1, 7),
+ ("m8[4D]", "m8[1M]", Casting.unsafe, None, None,
# give full values based on NumPy 1.19.x
[-2**63, 0, 0, 1314, -1315, 564442610])])
- def test_time_to_time(self, from_dt, to_dt, expected_casting, nom, denom):
+ def test_time_to_time(self, from_dt, to_dt,
+ expected_casting, expected_view_off,
+ nom, denom):
from_dt = np.dtype(from_dt)
if to_dt is not None:
to_dt = np.dtype(to_dt)
@@ -428,10 +434,12 @@ class TestCasting:
DType = type(from_dt)
cast = get_castingimpl(DType, DType)
- casting, (from_res, to_res) = cast._resolve_descriptors((from_dt, to_dt))
+ casting, (from_res, to_res), view_off = cast._resolve_descriptors(
+ (from_dt, to_dt))
assert from_res is from_dt
assert to_res is to_dt or to_dt is None
assert casting == expected_casting
+ assert view_off == expected_view_off
if nom is not None:
expected_out = (values * nom // denom).view(to_res)
@@ -476,9 +484,11 @@ class TestCasting:
expected_length = get_expected_stringlength(other_dt)
string_dt = np.dtype(f"{string_char}{expected_length}")
- safety, (res_other_dt, res_dt) = cast._resolve_descriptors((other_dt, None))
+ safety, (res_other_dt, res_dt), view_off = cast._resolve_descriptors(
+ (other_dt, None))
assert res_dt.itemsize == expected_length * fact
assert safety == Casting.safe # we consider to string casts "safe"
+ assert view_off is None
assert isinstance(res_dt, string_DT)
# These casts currently implement changing the string length, so
@@ -490,19 +500,24 @@ class TestCasting:
expected_safety = Casting.same_kind
to_dt = self.string_with_modified_length(string_dt, change_length)
- safety, (_, res_dt) = cast._resolve_descriptors((other_dt, to_dt))
+ safety, (_, res_dt), view_off = cast._resolve_descriptors(
+ (other_dt, to_dt))
assert res_dt is to_dt
assert safety == expected_safety
+ assert view_off is None
# The opposite direction is always considered unsafe:
cast = get_castingimpl(string_DT, other_DT)
- safety, _ = cast._resolve_descriptors((string_dt, other_dt))
+ safety, _, view_off = cast._resolve_descriptors((string_dt, other_dt))
assert safety == Casting.unsafe
+ assert view_off is None
cast = get_castingimpl(string_DT, other_DT)
- safety, (_, res_dt) = cast._resolve_descriptors((string_dt, None))
+ safety, (_, res_dt), view_off = cast._resolve_descriptors(
+ (string_dt, None))
assert safety == Casting.unsafe
+ assert view_off is None
assert other_dt is res_dt # returns the singleton for simple dtypes
@pytest.mark.parametrize("string_char", ["S", "U"])
@@ -521,7 +536,8 @@ class TestCasting:
cast = get_castingimpl(type(other_dt), string_DT)
cast_back = get_castingimpl(string_DT, type(other_dt))
- _, (res_other_dt, string_dt) = cast._resolve_descriptors((other_dt, None))
+ _, (res_other_dt, string_dt), _ = cast._resolve_descriptors(
+ (other_dt, None))
if res_other_dt is not other_dt:
# do not support non-native byteorder, skip test in that case
@@ -580,13 +596,16 @@ class TestCasting:
expected_length = other_dt.itemsize // div
string_dt = np.dtype(f"{string_char}{expected_length}")
- safety, (res_other_dt, res_dt) = cast._resolve_descriptors((other_dt, None))
+ safety, (res_other_dt, res_dt), view_off = cast._resolve_descriptors(
+ (other_dt, None))
assert res_dt.itemsize == expected_length * fact
assert isinstance(res_dt, string_DT)
+ expected_view_off = None
if other_dt.char == string_char:
if other_dt.isnative:
- expected_safety = Casting.no | Casting.cast_is_view
+ expected_safety = Casting.no
+ expected_view_off = 0
else:
expected_safety = Casting.equiv
elif string_char == "U":
@@ -594,13 +613,19 @@ class TestCasting:
else:
expected_safety = Casting.unsafe
+ assert view_off == expected_view_off
assert expected_safety == safety
for change_length in [-1, 0, 1]:
to_dt = self.string_with_modified_length(string_dt, change_length)
- safety, (_, res_dt) = cast._resolve_descriptors((other_dt, to_dt))
+ safety, (_, res_dt), view_off = cast._resolve_descriptors(
+ (other_dt, to_dt))
assert res_dt is to_dt
+ if change_length <= 0:
+ assert view_off == expected_view_off
+ else:
+ assert view_off is None
if expected_safety == Casting.unsafe:
assert safety == expected_safety
elif change_length < 0:
@@ -655,12 +680,16 @@ class TestCasting:
object_dtype = type(np.dtype(object))
cast = get_castingimpl(object_dtype, type(dtype))
- safety, (_, res_dt) = cast._resolve_descriptors((np.dtype("O"), dtype))
+ safety, (_, res_dt), view_off = cast._resolve_descriptors(
+ (np.dtype("O"), dtype))
assert safety == Casting.unsafe
+ assert view_off is None
assert res_dt is dtype
- safety, (_, res_dt) = cast._resolve_descriptors((np.dtype("O"), None))
+ safety, (_, res_dt), view_off = cast._resolve_descriptors(
+ (np.dtype("O"), None))
assert safety == Casting.unsafe
+ assert view_off is None
assert res_dt == dtype.newbyteorder("=")
@pytest.mark.parametrize("dtype", simple_dtype_instances())
@@ -669,8 +698,10 @@ class TestCasting:
object_dtype = type(np.dtype(object))
cast = get_castingimpl(type(dtype), object_dtype)
- safety, (_, res_dt) = cast._resolve_descriptors((dtype, None))
+ safety, (_, res_dt), view_off = cast._resolve_descriptors(
+ (dtype, None))
assert safety == Casting.safe
+ assert view_off is None
assert res_dt is np.dtype("O")
@pytest.mark.parametrize("casting", ["no", "unsafe"])
@@ -681,6 +712,71 @@ class TestCasting:
assert np.can_cast("V4", dtype, casting=casting) == expected
assert np.can_cast(dtype, "V4", casting=casting) == expected
+ @pytest.mark.parametrize(["to_dt", "expected_off"],
+ [ # Same as `from_dt` but with both fields shifted:
+ (np.dtype({"names": ["a", "b"], "formats": ["i4", "f4"],
+ "offsets": [2, 6]}), -2),
+ # Additional change of the names
+ # TODO: Tests will need changing for order vs. name based casting:
+ (np.dtype({"names": ["b", "a"], "formats": ["f4", "i4"],
+ "offsets": [6, 2]}), -2),
+ # Incompatible field offset change (offsets -2 and 0)
+ (np.dtype({"names": ["b", "a"], "formats": ["f4", "i4"],
+ "offsets": [6, 0]}), None)])
+ def test_structured_field_offsets(self, to_dt, expected_off):
+ # This checks the cast-safety and view offset for swapped and "shifted"
+ # fields which are viewable
+ from_dt = np.dtype({"names": ["a", "b"],
+ "formats": ["i4", "f4"],
+ "offsets": [0, 4]})
+ cast = get_castingimpl(type(from_dt), type(to_dt))
+ safety, _, view_off = cast._resolve_descriptors((from_dt, to_dt))
+ assert safety == Casting.equiv
+ # Shifting the original data pointer by -2 will align both by
+ # effectively adding 2 bytes of spacing before `from_dt`.
+ assert view_off == expected_off
+
+ @pytest.mark.parametrize(("from_dt", "to_dt", "expected_off"), [
+ # Subarray cases:
+ ("i", "(1,1)i", 0),
+ ("(1,1)i", "i", 0),
+ ("(2,1)i", "(2,1)i", 0),
+ # field cases (field to field is tested explicitly also):
+ ("i", dict(names=["a"], formats=["i"], offsets=[2]), -2),
+ (dict(names=["a"], formats=["i"], offsets=[2]), "i", 2),
+ # Currently considered not viewable, due to multiple fields
+ # even though they overlap (maybe we should not allow that?)
+ ("i", dict(names=["a", "b"], formats=["i", "i"], offsets=[2, 2]),
+ None),
+ # different number of fields can't work, should probably just fail
+ # so it never reports "viewable":
+ ("i,i", "i,i,i", None),
+ # Unstructured void cases:
+ ("i4", "V3", 0), # void smaller or equal
+ ("i4", "V4", 0), # void smaller or equal
+ ("i4", "V10", None), # void is larger (no view)
+ ("O", "V4", None), # currently reject objects for view here.
+ ("O", "V8", None), # currently reject objects for view here.
+ ("V4", "V3", 0),
+ ("V4", "V4", 0),
+ ("V3", "V4", None),
+ # Note that currently void-to-other cast goes via byte-strings
+ # and is not a "view" based cast like the opposite direction:
+ ("V4", "i4", None),
+ # completely invalid/impossible cast:
+ ("i,i", "i,i,i", None),
+ ])
+ def test_structured_view_offsets_paramteric(
+ self, from_dt, to_dt, expected_off):
+ # TODO: While this test is fairly thorough, right now, it does not
+ # really test some paths that may have nonzero offsets (they don't
+ # really exists).
+ from_dt = np.dtype(from_dt)
+ to_dt = np.dtype(to_dt)
+ cast = get_castingimpl(type(from_dt), type(to_dt))
+ _, _, view_off = cast._resolve_descriptors((from_dt, to_dt))
+ assert view_off == expected_off
+
@pytest.mark.parametrize("dtype", np.typecodes["All"])
def test_object_casts_NULL_None_equivalence(self, dtype):
# None to <other> casts may succeed or fail, but a NULL'ed array must
diff --git a/numpy/testing/print_coercion_tables.py b/numpy/testing/print_coercion_tables.py
index 3a447cd2d..c1d4cdff8 100755
--- a/numpy/testing/print_coercion_tables.py
+++ b/numpy/testing/print_coercion_tables.py
@@ -87,11 +87,12 @@ def print_new_cast_table(*, can_cast=True, legacy=False, flags=False):
from numpy.core._multiarray_tests import get_all_cast_information
cast_table = {
- 0 : "#", # No cast (classify as equivalent here)
- 1 : "#", # equivalent casting
- 2 : "=", # safe casting
- 3 : "~", # same-kind casting
- 4 : ".", # unsafe casting
+ -1: " ",
+ 0: "#", # No cast (classify as equivalent here)
+ 1: "#", # equivalent casting
+ 2: "=", # safe casting
+ 3: "~", # same-kind casting
+ 4: ".", # unsafe casting
}
flags_table = {
0 : "▗", 7: "█",