Skip to content
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

DEP: Shift correlate mode parsing to C and deprecate inexact matches #17492

Merged
merged 8 commits into from Mar 18, 2021

Conversation

aitikgupta
Copy link
Contributor

Fixes #17476
Also, see #16056

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution, although this isn't quite right.

I'd actually be very tempted to move this parameter parsing to C, so that it can be handled in exactly the same way as the other arguments like this. Would you be ok to try that instead?

numpy/core/numeric.py Outdated Show resolved Hide resolved
numpy/core/numeric.py Outdated Show resolved Hide resolved
Copy link
Contributor

@rossbar rossbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - the deprecation should not change the behavior, but warn the user that the behavior will change when the deprecation expires.

A few comments on the tests as well since I had looked at them.

numpy/core/tests/test_numeric.py Outdated Show resolved Hide resolved
numpy/core/tests/test_numeric.py Outdated Show resolved Hide resolved
@mattip
Copy link
Member

mattip commented Oct 8, 2020

Close/reopen to restart builds

@mattip mattip closed this Oct 8, 2020
@mattip mattip reopened this Oct 8, 2020
rossbar
rossbar previously requested changes Oct 8, 2020
Copy link
Contributor

@rossbar rossbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change still results in modified behavior. For example, on master np.convolve(*args, mode="silly") will result in mode 1 being used, whereas in the PR, the same call will result in 2. The deprecation should only warn the user of a pending behavior change, not silently change the behavior.

I would recommend getting rid of the default_mode addition and see if you can come up with a minimal change for raising the warning.

@aitikgupta
Copy link
Contributor Author

This change still results in modified behavior. For example, on master np.convolve(*args, mode="silly") will result in mode 1 being used, whereas in the PR, the same call will result in 2. The deprecation should only warn the user of a pending behavior change, not silently change the behavior.

I would recommend getting rid of the default_mode addition and see if you can come up with a minimal change for raising the warning.

I raised this question here: #17492 (comment)
That is what I intended to start off with, but I'll just stick to the deprecation part here, I'll update the PR. 👍🏼

@eric-wieser
Copy link
Member

I think my comment above might have gotten lost:

I'd actually be very tempted to move this parameter parsing to C, so that it can be handled in exactly the same way as the other arguments like this. Would you be ok to try that instead?

@aitikgupta
Copy link
Contributor Author

I think my comment above might have gotten lost:

I'd actually be very tempted to move this parameter parsing to C, so that it can be handled in exactly the same way as the other arguments like this. Would you be ok to try that instead?

I actually missed it amid suggestions. I wouldn't mind, could you point me to a right path to start digging in?

@eric-wieser
Copy link
Member

eric-wieser commented Oct 8, 2020

Sure, here's some pointers: The code that parses the mode argument to correlate is here:

static PyObject *
array_correlate(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds)
{
PyObject *shape, *a0;
int mode = 0;
static char *kwlist[] = {"a", "v", "mode", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|i:correlate", kwlist,
&a0, &shape, &mode)) {
return NULL;
}
return PyArray_Correlate(a0, shape, mode);
}

My suggestion would be to change that to something like:

if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O&:correlate", kwlist, 
                 &a0, &shape, PyArray_CorrelateModeConverter, &mode))

where PyArray_CorrelateModeConverter would look something like:

static int clipmode_parser(char const *str, Py_ssize_t length, void *data)
{
NPY_CLIPMODE *val = (NPY_CLIPMODE *)data;
int is_exact = 0;
if (length < 1) {
return -1;
}
if (str[0] == 'C' || str[0] == 'c') {
*val = NPY_CLIP;
is_exact = (length == 4 && strcmp(str, "clip") == 0);
}
else if (str[0] == 'W' || str[0] == 'w') {
*val = NPY_WRAP;
is_exact = (length == 4 && strcmp(str, "wrap") == 0);
}
else if (str[0] == 'R' || str[0] == 'r') {
*val = NPY_RAISE;
is_exact = (length == 5 && strcmp(str, "raise") == 0);
}
else {
return -1;
}
/* Filters out the case sensitive/non-exact
* match inputs and other inputs and outputs DeprecationWarning
*/
if (!is_exact) {
/* Numpy 1.20, 2020-05-19 */
if (DEPRECATE("inexact matches and case insensitive matches "
"for clip mode are deprecated, please use "
"one of 'clip', 'raise', or 'wrap' instead.") < 0) {
return -1;
}
}
return 0;
}
/*NUMPY_API
* Convert an object to NPY_RAISE / NPY_CLIP / NPY_WRAP
*/
NPY_NO_EXPORT int
PyArray_ClipmodeConverter(PyObject *object, NPY_CLIPMODE *val)
{
if (object == NULL || object == Py_None) {
*val = NPY_RAISE;
}
else if (PyBytes_Check(object) || PyUnicode_Check(object)) {
return string_converter_helper(
object, (void *)val, clipmode_parser, "clipmode",
"must be one of 'clip', 'raise', or 'wrap'");
}
else {
/* For users passing `np.RAISE`, `np.WRAP`, `np.CLIP` */
int number = PyArray_PyIntAsInt(object);
if (error_converting(number)) {
goto fail;
}
if (number <= (int) NPY_RAISE
&& number >= (int) NPY_CLIP) {
*val = (NPY_CLIPMODE) number;
}
else {
PyErr_Format(PyExc_ValueError,
"integer clipmode must be np.RAISE, np.WRAP, or np.CLIP");
}
}
return NPY_SUCCEED;
fail:
PyErr_SetString(PyExc_TypeError,
"clipmode not understood");
return NPY_FAIL;
}

@aitikgupta
Copy link
Contributor Author

aitikgupta commented Oct 9, 2020

Hey @eric-wieser I could use some help here. It took me a while but I was able to shift parameter parsing completely to C, it worked well locally until I did something wrong during the last few changes - build is failing (KeyError of the function name).
I think I worked it out globally using __init__ files, but that wasn't the solid solution.

I'm thinking I should add a Global C Pointer key value in numpy_api?
Pardon if these might be very beginner questions 😅

I've incorporated your suggestion, and haven't changed the behaviour. (You can still review the changes, the build error should be solvable)

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't need to make this part of the public C API - which should also fix your build error

numpy/core/src/multiarray/conversion_utils.c Outdated Show resolved Hide resolved
numpy/core/src/multiarray/conversion_utils.c Outdated Show resolved Hide resolved
@aitikgupta aitikgupta force-pushed the unwanted-mode-dep branch 2 times, most recently from 9cf6fb9 to e811a8f Compare October 9, 2020 14:27
@aitikgupta
Copy link
Contributor Author

The build works locally, should be fine for review now. 👍🏼

valid_mode = np.correlate(d, k, mode='v')
assert_array_equal(valid_mode, default_mode)
# integer mode
assert_array_equal(np.correlate(d, k, mode=0), valid_mode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test of an invalid integer too?

Also, it may make sense to add a test here too:

static PyObject *
run_clipmode_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
NPY_CLIPMODE mode;
if (!PyArg_ParseTuple(args, "O&", PyArray_ClipmodeConverter, &mode)) {
return NULL;
}
switch (mode) {
case NPY_CLIP: return PyUnicode_FromString("NPY_CLIP");
case NPY_WRAP: return PyUnicode_FromString("NPY_WRAP");
case NPY_RAISE: return PyUnicode_FromString("NPY_RAISE");
}
return PyLong_FromLong(mode);
}

{"run_casting_converter",
run_casting_converter,
METH_VARARGS, NULL},

class TestCastingConverter(StringConverterTestCase):
""" Tests of PyArray_CastingConverter """
conv = mt.run_casting_converter
case_insensitive = False
exact_match = True
def test_valid(self):
self._check("no", "NPY_NO_CASTING")
self._check("equiv", "NPY_EQUIV_CASTING")
self._check("safe", "NPY_SAFE_CASTING")
self._check("same_kind", "NPY_SAME_KIND_CASTING")
self._check("unsafe", "NPY_UNSAFE_CASTING")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created the tests, but I can't seem to find a way to make PyArray_CorrelatemodeConverter function visible like PyArray_ClipmodeConverter in this file:

static PyObject *
run_clipmode_converter(PyObject* NPY_UNUSED(self), PyObject *args)
{
NPY_CLIPMODE mode;
if (!PyArg_ParseTuple(args, "O&", PyArray_ClipmodeConverter, &mode)) {
return NULL;
}
switch (mode) {
case NPY_CLIP: return PyUnicode_FromString("NPY_CLIP");
case NPY_WRAP: return PyUnicode_FromString("NPY_WRAP");
case NPY_RAISE: return PyUnicode_FromString("NPY_RAISE");
}
return PyLong_FromLong(mode);
}

If I directly #include "conversion_utils.h" it throws a bunch of errors while building, which is understandable.
I think PyArray_ClipmodeConverter is visible to this file as it is a global C API.
Or is there something I'm missing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think PyArray_ClipmodeConverter is visible to this file as it is a global C API.

Hmm, that's possible.

If I directly #include "conversion_utils.h" it throws a bunch of errors while building, which is understandable.

Can you give an example of those errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the full traceback, it seems like a compiler issue?

In file included from numpy/core/include/numpy/ndarrayobject.h:21:0,
                 from numpy/core/include/numpy/arrayobject.h:4,
                 from numpy/core/src/multiarray/_multiarray_tests.c.src:5:
numpy/core/include/numpy/__multiarray_api.h:1090:12: error: expected identifier or ‘(’ before ‘int’
         (*(int (*)(PyObject *, PyArray_Dims *)) \
            ^
numpy/core/src/multiarray/conversion_utils.h:7:1: note: in expansion of macro ‘PyArray_IntpConverter’
 PyArray_IntpConverter(PyObject *obj, PyArray_Dims *seq);
 ^~~~~~~~~~~~~~~~~~~~~
numpy/core/include/numpy/__multiarray_api.h:1445:10: error: expected ‘)’ before ‘PyArray_API’
          PyArray_API[298])
          ^
numpy/core/src/multiarray/conversion_utils.h:49:1: note: in expansion of macro ‘PyArray_SelectkindConverter’
 PyArray_SelectkindConverter(PyObject *obj, NPY_SELECTKIND *selectkind);
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looks like your diagnosis is correct; multiarray_tests requires things to be in the public API. I think it's ok to leave things as is for now, thanks for investigating

@aitikgupta
Copy link
Contributor Author

All builds pass successfully, think we can merge it now?

@rossbar rossbar dismissed their stale review October 12, 2020 20:03

Moved arg parsing to C

@aitikgupta aitikgupta changed the title DEP: Deprecate inexact matches for mode in np.correlate and np.convolve DEP: Shift correlate mode parsing to C and deprecate inexact matches Oct 22, 2020
@aitikgupta
Copy link
Contributor Author

/cc @eric-wieser @rossbar

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ought to have a release note too - sorry for forgetting about this for so long!

@aitikgupta
Copy link
Contributor Author

sorry for forgetting about this for so long!

That's completely fine :)

@aitikgupta
Copy link
Contributor Author

@eric-wieser Do you want me to squash the commits?

@mattip
Copy link
Member

mattip commented Feb 6, 2021

@eric-wieser could you take one last look?

Base automatically changed from master to main March 4, 2021 02:05
@mattip
Copy link
Member

mattip commented Mar 18, 2021

@aitikgupta this PR was approved but seems to have gotten lost. Now there are merge conflicts. Could you merge with main or rebase it to clear the path for merging it?

@eric-wieser any need for more review?

@eric-wieser
Copy link
Member

I don't think this needs any more review; just the merge conflicts resolving after the argument parsing change

@mattip
Copy link
Member

mattip commented Mar 18, 2021

rebased to clear merge conflicts

@aitikgupta
Copy link
Contributor Author

just the merge conflicts resolving after the argument parsing change

@mattip did the heavy lifting before I could jump in :D

@mattip mattip merged commit ddbff08 into numpy:main Mar 18, 2021
@mattip
Copy link
Member

mattip commented Mar 18, 2021

Thanks @aitikgupta, sorry it took so long to get this merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Core: Convolve and Correlate mode check in core/numeric.py
4 participants