8000 MAINT: Use intp output param viewable casts/methods (#20176) · numpy/numpy@229c175 · GitHub
[go: up one dir, main page]

Skip to content

Commit 229c175

Browse files
sebergmhvk
andauthored
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>
1 parent 1531820 commit 229c175

17 files changed

+509
-210
lines changed

numpy/core/include/numpy/experimental_dtype_api.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@
8282
* The new DType API is designed in a way to make it potentially useful for
8383
* alternative "array-like" implementations. This will require careful
8484
* exposure of details and functions and is not part of this experimental API.
85+
*
86+
* Brief (incompatibility) changelog
87+
* =================================
88+
*
89+
* 2. None (only additions).
90+
* 3. New `npy_intp *view_offset` argument for `resolve_descriptors`.
91+
* This replaces the `NPY_CAST_IS_VIEW` flag. It can be set to 0 if the
92+
* operation is a view, and is pre-initialized to `NPY_MIN_INTP` indicating
93+
* that the operation is not a view.
8594
*/
8695

8796
#ifndef NUMPY_CORE_INCLUDE_NUMPY_EXPERIMENTAL_DTYPE_API_H_
@@ -206,16 +215,6 @@ typedef int _ufunc_addpromoter_func(
206215
#define PyUFunc_AddPromoter \
207216
(*(_ufunc_addpromoter_func *)(__experimental_dtype_api_table[1]))
208217

209-
/*
210-
* In addition to the normal casting levels, NPY_CAST_IS_VIEW indicates
211-
* that no cast operation is necessary at all (although a copy usually will be)
212-
*
213-
* NOTE: The most likely modification here is to add an additional
214-
* `view_offset` output to resolve_descriptors. If set, it would
215-
* indicate both that it is a view and what offset to use. This means that
216-
* e.g. `arr.imag` could be implemented by an ArrayMethod.
217-
*/
218-
#define NPY_CAST_IS_VIEW _NPY_CAST_IS_VIEW
219218

220219
/*
221220
* The resolve descriptors function, must be able to handle NULL values for
@@ -236,7 +235,8 @@ typedef NPY_CASTING (resolve_descriptors_function)(
236235
/* Input descriptors (instances). Outputs may be NULL. */
237236
PyArray_Descr **given_descrs,
238237
/* Exact loop descriptors to use, must not hold references on error */
239-
PyArray_Descr **loop_descrs);
238+
PyArray_Descr **loop_descrs,
239+
npy_intp *view_offset);
240240

241241
/* NOT public yet: Signature needs adapting as external API. */
242242
#define _NPY_METH_get_loop 2

numpy/core/include/numpy/ndarraytypes.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,6 @@ typedef enum {
221221
NPY_SAME_KIND_CASTING=3,
222222
/* Allow any casts */
223223
NPY_UNSAFE_CASTING=4,
224-
/*
225-
* Flag to allow signalling that a cast is a view, this flag is not
226-
* valid when requesting a cast of specific safety.
227-
* _NPY_CAST_IS_VIEW|NPY_EQUIV_CASTING means the same as NPY_NO_CASTING.
228-
*/
229-
// TODO-DTYPES: Needs to be documented.
230-
_NPY_CAST_IS_VIEW = 1 << 16,
231224
} NPY_CASTING;
232225

233226
typedef enum {

numpy/core/src/multiarray/_multiarray_tests.c.src

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,20 +1067,18 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
10671067
PyArrayMethodObject *cast = (PyArrayMethodObject *)cast_obj;
10681068

10691069
/* Pass some information about this cast out! */
1070-
PyObject *cast_info = Py_BuildValue("{sOsOsisisisisisssi}",
1070+
PyObject *cast_info = Py_BuildValue("{sOsOsisisisisiss}",
10711071
"from", from_dtype,
10721072
"to", to_dtype,
10731073
"legacy", (cast->name != NULL &&
10741074
strncmp(cast->name, "legacy_", 7) == 0),
1075-
"casting", cast->casting & ~_NPY_CAST_IS_VIEW,
1075+
"casting", cast->casting,
10761076
"requires_pyapi", cast->flags & NPY_METH_REQUIRES_PYAPI,
10771077
"supports_unaligned",
10781078
cast->flags & NPY_METH_SUPPORTS_UNALIGNED,
10791079
"no_floatingpoint_errors",
10801080
cast->flags & NPY_METH_NO_FLOATINGPOINT_ERRORS,
1081-
"name", cast->name,
1082-
"cast_is_view",
1083-
cast->casting & _NPY_CAST_IS_VIEW);
1081+
"name", cast->name);
10841082
if (cast_info == NULL) {
10851083
goto fail;
10861084
}

numpy/core/src/multiarray/array_method.c

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,19 @@
4848
*
4949
* We could allow setting the output descriptors specifically to simplify
5050
* this step.
51+
*
52+
* Note that the default version will indicate that the cast can be done
53+
* as using `arr.view(new_dtype)` if the default cast-safety is
54+
* set to "no-cast". This default function cannot be used if a view may
55+
* be sufficient for casting but the cast is not always "no-cast".
5156
*/
5257
static NPY_CASTING
5358
default_resolve_descriptors(
5459
PyArrayMethodObject *method,
5560
PyArray_DTypeMeta **dtypes,
5661
PyArray_Descr **input_descrs,
57-
PyArray_Descr **output_descrs)
62+
PyArray_Descr **output_descrs,
63+
npy_intp *view_offset)
5864
{
5965
int nin = method->nin;
6066
int nout = method->nout;
@@ -76,6 +82,13 @@ default_resolve_descriptors(
7682
* abstract ones or unspecified outputs). We can use the common-dtype
7783
* operation to provide a default here.
7884
*/
85+
if (method->casting == NPY_NO_CASTING) {
86+
/*
87+
* By (current) definition no-casting should imply viewable. This
88+
* is currently indicated for example for object to object cast.
89+
*/
90+
*view_offset = 0;
91+
}
7992
return method->casting;
8093

8194
fail:
@@ -102,9 +115,10 @@ is_contiguous(
102115
/**
103116
* The default method to fetch the correct loop for a cast or ufunc
104117
* (at the time of writing only casts).
105-
* The default version can return loops explicitly registered during method
106-
* creation. It does specialize contiguous loops, although has to check
107-
* all descriptors itemsizes for this.
118+
* Note that the default function provided here will only indicate that a cast
119+
* can be done as a view (i.e., arr.view(new_dtype)) when this is trivially
120+
* true, i.e., for cast safety "no-cast". It will not recognize view as an
121+
* option for other casts (e.g., viewing '>i8' as '>i4' with an offset of 4).
108122
*
109123
* @param context
110124
* @param aligned
@@ -166,7 +180,7 @@ validate_spec(PyArrayMethod_Spec *spec)
166180
"not exceed %d. (method: %s)", NPY_MAXARGS, spec->name);
167181
return -1;
168182
}
169-
switch (spec->casting & ~_NPY_CAST_IS_VIEW) {
183+
switch (spec->casting) {
170184
case NPY_NO_CASTING:
171185
case NPY_EQUIV_CASTING:
172186
case NPY_SAFE_CASTING:
@@ -495,8 +509,9 @@ boundarraymethod_dealloc(PyObject *self)
495509

496510

497511
/*
498-
* Calls resolve_descriptors() and returns the casting level and the resolved
499-
* descriptors as a tuple. If the operation is impossible returns (-1, None).
512+
* Calls resolve_descriptors() and returns the casting level, the resolved
513+
* descriptors as a tuple, and a possible view-offset (integer or None).
514+
* If the operation is impossible returns (-1, None, None).
500515
* May raise an error, but usually should not.
501516
* The function validates the casting attribute compared to the returned
502517
* casting level.
@@ -551,14 +566,15 @@ boundarraymethod__resolve_descripors(
551566
}
552567
}
553568

569+
npy_intp view_offset = NPY_MIN_INTP;
554570
NPY_CASTING casting = self->method->resolve_descriptors(
555-
self->method, self->dtypes, given_descrs, loop_descrs);
571+
self->method, self->dtypes, given_descrs, loop_descrs, &view_offset);
556572

557573
if (casting < 0 && PyErr_Occurred()) {
558574
return NULL;
559575
}
560576
else if (casting < 0) {
561-
return Py_BuildValue("iO", casting, Py_None);
577+
return Py_BuildValue("iO", casting, Py_None, Py_None);
562578
}
563579

564580
PyObject *result_tuple = PyTuple_New(nin + nout);
@@ -570,9 +586,22 @@ boundarraymethod__resolve_descripors(
570586
PyTuple_SET_ITEM(result_tuple, i, (PyObject *)loop_descrs[i]);
571587
}
572588

589+
PyObject *view_offset_obj;
590+
if (view_offset == NPY_MIN_INTP) {
591+
Py_INCREF(Py_None);
592+
view_offset_obj = Py_None;
593+
}
594+
else {
595+
view_offset_obj = PyLong_FromSsize_t(view_offset);
596+
if (view_offset_obj == NULL) {
597+
Py_DECREF(result_tuple);
598+
return NULL;
599+
}
600+
}
601+
573602
/*
574-
* The casting flags should be the most generic casting level (except the
575-
* cast-is-view flag. If no input is parametric, it must match exactly.
603+
* The casting flags should be the most generic casting level.
604+
* If no input is parametric, it must match exactly.
576605
*
577606
* (Note that these checks are only debugging checks.)
578607
*/
@@ -584,14 +613,15 @@ boundarraymethod__resolve_descripors(
584613
}
585614
}
586615
if (self->method->casting != -1) {
587-
NPY_CASTING cast = casting & ~_NPY_CAST_IS_VIEW;
616+
NPY_CASTING cast = casting;
588617
if (self->method->casting !=
589618
PyArray_MinCastSafety(cast, self->method->casting)) {
590619
PyErr_Format(PyExc_RuntimeError,
591620
"resolve_descriptors cast level did not match stored one. "
592621
"(set level is %d, got %d for method %s)",
593622
self->method->casting, cast, self->method->name);
594623
Py_DECREF(result_tuple);
624+
Py_DECREF(view_offset_obj);
595625
return NULL;
596626
}
597627
if (!parametric) {
@@ -608,12 +638,13 @@ boundarraymethod__resolve_descripors(
608638
"(set level is %d, got %d for method %s)",
609639
self->method->casting, cast, self->method->name);
610640
Py_DECREF(result_tuple);
641+
Py_DECREF(view_offset_obj);
611642
return NULL;
612643
}
613644
}
614645
}
615646

616-
return Py_BuildValue("iN", casting, result_tuple);
647+
return Py_BuildValue("iNN", casting, result_tuple, view_offset_obj);
617648
}
618649

619650

@@ -694,8 +725,9 @@ boundarraymethod__simple_strided_call(
694725
return NULL;
695726
}
696727

728+
npy_intp view_offset = NPY_MIN_INTP;
697729
NPY_CASTING casting = self->method->resolve_descriptors(
698-
self->method, self->dtypes, descrs, out_descrs);
730+
self->method, self->dtypes, descrs, out_descrs, &view_offset);
699731

700732
if (casting < 0) {
701733
PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL;

numpy/core/src/multiarray/array_method.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ typedef NPY_CASTING (resolve_descriptors_function)(
7070
struct PyArrayMethodObject_tag *method,
7171
PyArray_DTypeMeta **dtypes,
7272
PyArray_Descr **given_descrs,
73-
PyArray_Descr **loop_descrs);
73+
PyArray_Descr **loop_descrs,
74+
npy_intp *view_offset);
7475

7576

7677
typedef int (get_loop_function)(

0 commit comments

Comments
 (0)
0