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

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

Merged
merged 5 commits into from
Nov 25, 2020

Conversation

seberg
Copy link
Member

@seberg seberg commented Sep 30, 2020

This PR is pretty big, we could probably split up some parts of it, e.g. the loops are not always used (although in large part tested if they are implemented)

The goal for this PR is currently to define all casts using the new machinery and add fairly thorough tests. Then we can put this in as functionality that is de-facto unused, but tested. A followup will then:

  1. Use this to implement np.can_cast
  2. Use it in the casting machinery.

Both of which should be limited changes after this is done (at least if we defer some optimization), but do change very central code in NumPy, so in the last dev meeting the preliminary plan was that we may defer changing this after the 1.20 release.


There is a lot to dissect in this PR, the basic design is that everything is stored on ArrayMethod objects (much like a ufunc loop+dtype resolver).


Some aspects, to draw attention to (although some of these should be clarified in NEP 43):

  • I decided in NEP 42 to return the casting safety, this is different from the fact that we currently pass in the safety we ask for. I like this design and it even adds
    • This also means that casts should not report custom errors: I think this is fine for now. Datetimes do have custom errors, but do not seem to use them for np.can_cast (only for scalars probably).
  • We use move_references flag for handling references together with buffers. Right now, we can just ignore that (keep the "flag" around), but when making this public we may have to think about it, e.g. add additional flags to the ArrayMethod to signal that it can move references.

Checklist before merging:

  • Currently defaults to using the new system, that must be switched before merging (but means a full test/coverage run here), and probably the flag added to 2 CI runs or so.

@seberg seberg marked this pull request as draft September 30, 2020 00:38
@seberg seberg force-pushed the restructure-casting branch 2 times, most recently from f3475c6 to 74a699c Compare October 17, 2020 00:58
@seberg
Copy link
Member Author

seberg commented Oct 19, 2020

OK, I realize that 4000 lines of new code is too big, but we have to start somewhere on this. Now that it is working as a drop in replacement for np.can_cast and at least those actual casts that are implemented are too some degree tested, it would be nice to get some feedback.

I don't like merging almost unused code, but one thing I could do is create a PR which only adds PyArrayMethod, etc. (and possibly a single ArrayMethod/cast), and then follow up with the rest of the changes, hopefully not in a single large PR.

So the question is what do you think, the only serious thing (aside from being a huge chunk of code) is probably the comment I put above around NPY_CASTING. Otherwise, this needs some eyes on:

  • The ArrayMethod design in array_method.h and array_method.c.
  • The changes in convert_datatype.c give an idea of how things are structured, which is probably useful to get a feel of the ArrayMethod.
  • Yes, is probably some cleanup still necessary in the current state, and a couple of new tests might be good, but that has little to do with the larger design.

Would it be possible to get some feedback on array_method.c and array_method.h (with an eye on an example in convert_datatype.c or the datetime.c casting implementations)? With that context I would be happy to split it out so we can merge it in somewhat more manageable chunks.

@seberg seberg marked this pull request as ready for review November 3, 2020 16:57
@seberg
Copy link
Member Author

seberg commented Nov 3, 2020

I probably will need gh-17706 to fix all the test failures (some new tests create problems). I removed the draft status, just in case that put anyone off from looking at this. This PR includes super important new infrastructure for new DTypes and I really need some review or any improvements here are mainly randomly kicking things and not too helpful.

But to be clear: There is some followup necessary probably, but overall it should be far enough that most of that followup can happen later. With two asides:

  • Testing both version in the CI matrix (I intentionally want the new version run the full test suit for now.
  • Any API question, the only real one being the NPY_CASTING that I individually commented above.

Copy link
Member

@mattip mattip left a comment

Choose a reason for hiding this comment

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

I did a high-level look through. I didn't see a way to break this into smaller PRs, except maybe the resolvers (which is not much code). On the other hand, being able to toggle this on and off will be helpful.

I think we should aim for making sure the API (and NEP 43) is correct since that will be harder to change in future releases, and merge this so it can make it into the 1.20 release. There should be more documentation, or places where this points to parts of NEP 43.

numpy/core/include/numpy/ndarraytypes.h Show resolved Hide resolved
numpy/conftest.py Outdated Show resolved Hide resolved
numpy/core/include/numpy/ndarraytypes.h Show resolved Hide resolved
@@ -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.
# This allows running the full test suit and testing with the new
# implementation. By default, use the new implementation only in release mode.
Copy link
Member

Choose a reason for hiding this comment

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

In the changelog entry you say something a little different

Suggested change
# implementation. By default, use the new implementation only in release mode.
# implementation. By default, this is None for this release of NumPy

@@ -468,6 +473,11 @@ 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 != "0" or (
NPY_USE_NEW_CASTINGIMPL is None and not is_released(config)):
Copy link
Member

Choose a reason for hiding this comment

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

The dependence on is_released is confusing. There should be only one way to turn this on and off.

Suggested change
NPY_USE_NEW_CASTINGIMPL is None and not is_released(config)):
NPY_USE_NEW_CASTINGIMPL is None:

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I will just set it to always of for now, we can just as well switch it to always on after branching 1.20. Hopefully we will delete the whole old branches fairly soon in any case.

numpy/core/src/multiarray/array_assign_array.c Outdated Show resolved Hide resolved
}

/* We find the common dtype of all inputs, and use it for the blanks */
assert(nin > 0); /* this function is not used */
Copy link
Member

Choose a reason for hiding this comment

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

better to raise an error than to crash-only-on-debug

Copy link
Member Author

Choose a reason for hiding this comment

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

Made it an error, it should be rejected at registration time (in the FromSpec function) or indicates a misuse where a DType was defined but then is missing from the context (if a user defined an ArrayMethod with all of these dtypes, they must also be passed into the context).

numpy/core/src/multiarray/array_method.c Outdated Show resolved Hide resolved
numpy/core/src/multiarray/array_method.c Show resolved Hide resolved

/*
* Describes casting within datetimes or timedelta
*/
Copy link
Member

Choose a reason for hiding this comment

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

This is the heart of why the new design is so much better. Nice.

@seberg seberg changed the title WIP: Implement casting using a new ArrayMethod to structure casting and ufuncs MAINT: Rewrite can-cast logic in terms of NEP 42 Nov 5, 2020
@seberg seberg force-pushed the restructure-casting branch 2 times, most recently from 9636312 to d1d5bfc Compare November 5, 2020 02:00
@seberg
Copy link
Member Author

seberg commented Nov 5, 2020

I had to squash everything, so the history is lost for now (I have a backup). The get_loop is now (almost) always NULL which removes 1000 lines or so of change (including some of the new tests). I addressed most of the current comments.

As a reminder, the only actual new public API is the change to NPY_CASTING, the important design choices that I think we are doing:

  • The general ArrayMethod design
  • The return value of resolve_descriptors (returning the casting values)
  • The get_loop function should exist, but the signature is absolutely open
  • The inner loop function signature is not even remotely set, I prefer to restructure casting and then fix the signature to be compatible to ufuncs right now.

@seberg
Copy link
Member Author

seberg commented Nov 10, 2020

Tests are all passing now. The doctest failures is real, it is because my code rejects np.can_cast("O", "V", casting="safe") (safe casting is default here, since it is not a structured void with a signle "O" field after all). The failure will thus go away if I switch the default back and only make a single job use the environment variable. I think it would be better to do that at the very end though.

Other than that, I currently only expect to add one or two tests based on what codecov says (although the bad coverage is largely due to untestable error code and the legacy code that is simply never used.

@mattip mattip added this to the 1.20.0 release milestone Nov 18, 2020
@mattip
Copy link
Member

mattip commented Nov 19, 2020

doctest is failing

@seberg
Copy link
Member Author

seberg commented Nov 19, 2020

@mattip yes, that is intentional right now. Because I wanted the full test suit to run with the changes. But there is this one small change (e.g. code coverage). But will change it later hopefully (or as soon as you show intention of no/few further fixups).

EDIT: Based on codecov, I also wanted to add 1-2 tests, although a lot of what it complains seems hard to trigger error paths.

@charris
Copy link
Member

charris commented Nov 24, 2020

Not quite ready yet?

@seberg
Copy link
Member Author

seberg commented Nov 25, 2020

Was just doing the last update, then run through tests and change the flag. Will finish tonight.

Casting from object uses inspection logic, so doesn't actually
end up in this path, and thus will not use (arguably incorrectly)
reuse the itemsize of the object dtype in any case.
Lets defer further touch ups to later... One more run, since the
last one errored (hopefully due to an old failure not merged
correctly)
@seberg
Copy link
Member Author

seberg commented Nov 25, 2020

OK, should have flipped the switch back and one random azure run hopefully including it. (So I think it should be OK to go in, I am sure there will be smaller changes, but those might as well happen later)

@seberg
Copy link
Member Author

seberg commented Nov 25, 2020

OK, tests are fine. Please ignore the coverage, I manually looked that it was pretty good (aside from some of the code in array_method.c which is simply not used much yet. There is simply a lot of dead code since the new cast logic is only well exercised if the NPY_USE_NEW_CASTINGIMPL=1 is set at compile time.

@charris
Copy link
Member

charris commented Nov 25, 2020

@mattip Ready?

Copy link
Member

@mattip mattip left a comment

Choose a reason for hiding this comment

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

I guess we should put this in, even though the chances it gets used during the 1.20 release cycle are slim. I am still not 100% happy with the API but as @seberg says it is all internal so we are free to modify it as we go.


PyArray_DTypeMeta **dtypes;
/* Operand descriptors, filled in by adjust_desciptors */
PyArray_Descr **descriptors;
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to give dtypes and descriptors less generic names. It still bothers me that both are input when really only one or the other is needed. In any case, in NEP 43, the dtypes field is capitalized Dtypes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I missed changing it in this PR already, the intention is to modify this to descriptors now, since it is not passed to resolve_descriptors.

@charris
Copy link
Member

charris commented Nov 25, 2020

OK, in it goes. If nothing else, later patches should be smaller...

@charris charris merged commit ba77419 into numpy:master Nov 25, 2020
@charris
Copy link
Member

charris commented Nov 25, 2020

Thanks Sebastian.

@seberg
Copy link
Member Author

seberg commented Nov 25, 2020

Yes, there will be API wiggling needed before exposure... But, on the up-side it is probably easier to get a feel for those things once the next step (and maybe the ufunc changes) are in the pipeline.

@seberg
Copy link
Member Author

seberg commented Nov 25, 2020

Thanks Matti! I know this is tough to move forward, and a large tricky project.

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.

3 participants