-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
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
WIP: gufunc core dimensions should not broadcast #5077
WIP: gufunc core dimensions should not broadcast #5077
Conversation
Some working code to substantiate the discussion. This currently disallows non-matching core dimensions, but prepends dimensions of size 1 as needed to complete the core dimensions. Some examples of the behavior... First the obvious ones:
The prepending dimensions of size 1 thing at work:
And a failure that needs a rewording of the error, because the non-matching dimension is a prepended 1, so the error is misleading:
|
I think that the core dimensions should not be filled out, i.e., if the signature is |
-1 on the dimension prepending until the mailing list discussion is resolved. (I really don't understand why anyone thinks this is a good idea? Why would we want the equivalent of |
Oh, now it sounds like Chuck and I are in agreement after all :-) |
btw you have universal build failure |
Yes, the failing tests are the ones checking that gufuncs do what this PR no longer allows... The don't-prepend-ones behavior is already in place for output args, so getting it for inputs is trivial. |
So now an exact match between the sizes of core dimensions is required, and all core dimensions must be present. I kind of like the result, but It feels like too big of a change to do so happily... |
If this goes in, and I'm in favor of that pending the discussion on the list, it needs to be documented in the release notes, and also in |
This is probably also a good opportunity to put together a more comprehensive set of tests that validate that the behavior is what we have been discussing. |
I have some functions that we can use for that if we want to eventually remove |
c309dd7
to
1f8e3df
Compare
@charris Take a look at the new code when you have a chance, and let me know what you think. I have tried to minimize code repetition, so I may have departed a little bit from what we discussed on the list. I haven't tested the new path (creating a ufunc with |
Probably best to replace the magic numbers "0", "1" and "2" by named |
So if I understand the plan correctly:
So, uh... Who is this strict mode for? It seems like no one will ever use I'm also curious why we aren't just treating this as a bug - the behaviour |
I tend to agree, we probably don't need the current mode (or at least for now we can assume we don't), and can opt instead for a normal deprecation. |
@njsmith The current function is in the API, so the choices are
I would normally pick 2 out of caution, but Jaime is suggesting that there may be more changes/options in the future,, so it seems reasonable to have one constructor for all the potential options. |
As a general rule, I think we should try hard to minimize the number of I don't really understand the future proofing argument - the new API @njsmith https://github.com/njsmith The current function is in the API,
I would normally pick 2 out of caution, but Jaime is suggesting that there — |
About the current behavior, I think the arrays must have at least as many dimensions as in the signature, but broadcasting within that smaller scope could be useful in some circumstances, so another option is to change the original to require the right sizes but continue the broadcasting, then add a new function that doesn't allow broadcasting. |
Can you give a concrete example of these "some circumstances"?
|
@njsmith It's already parameterized internally, although that is an implementation detail, the question is whether of not it would be useful to expose that parameterization. That said, I'm sympathetic to the idea of just adding new functions to the API, designing for the (unknown) future sometimes creates a mess. One example I can think of would be multiplication of a stack of matrices by a stack of diagonal matrices. An efficient way to do that, instead of the full matrix multiplication, would be to store the diagonals as (1,n) matrices and broadcast the |
Another option is to extend the signature notation, allowing an explicit |
For example, |
For the broadcasting matrix-times-diagonal matrix case, isn't that the way You seem very gung ho about figuring out how to expose this but I feel
|
It was a suggestion on the list, there was a "maybe" attached. I was hoping for discussion, which we now have ;) The current I'm rather liking the idea of letting the signature specify that behavior. |
Maybe I am just being dense but I still have no idea how you are imagining
|
Matmul is a separate issue, this is just about gufuncs in general. |
Yes, but I still haven't seen any example I understand of why any gufunc
|
I think my reference for what I would not like to happen is @seberg's rewrite of indexing: everything looked great to everyone, all the new behaviors were sensible and better than the ones they were replacing... Then we put it out and seemed to break everyone's code, and had to go back and make some of the old behavior fit in again in a hurry. If we extend the signature parsing code following Nathaniel's idea of "a trailing ? means optional dimension", we could also go with "a trailing means core dimension broadcasts." This will add the most complexity and may still create interesting backwards compatibility issues if anyone is relying on broadcast of core dimensions in their code, so I am -0.5 on it, and would rather go the "this is a bug" route. If we add a new constructor to the API, I would also change all the existing linalg gufuncs to the new behavior, add deprecation warnings to the old constructor, and eventually, if no major issue is found, make both constructors an alias of each other and remove the old behavior. But quite frankly, I don't care if the bike shed gets painted green or orange... |
The thought is that you can stack vectors with broadcasting explicitly as The simplest options at this point, ignoring all these possibilites, are
The second gives backwards compatibility in case there is a function in the wild depending on the old behavior, the first is a bit of a gamble. Probably safe, but I've been surprised before... |
So +1 for Jaime's approach. We can keep the other possibilities in mind for the future. |
Here is where we differ. If I thought there was a great burden in supporting the current behavior, I would agree with you. But I don't see that. For the old behavior, we can leave support and testing as is and wait for complaints, if any. For the new behavior, tests need to be written in any case, so that part is a wash. Documentation can mention up front the distinction, and that should not be difficult to understand. And the code itself is not much more complicated; code for the new behavior is available, the old code is still there, and the choice between the two is determined by the value of |
I've searched github and Google Code for any independent uses of |
Are you thinking of going cold turkey? Or will we still want to support the old behavior with a deprecation warning for the next release cycle? |
I figure it's cold turkey and keep the current name. If we add a new function we might as well support both, the upkeep isn't much. |
Here's an example of the kind of "costs" I'm thinking of: suppose scipy or whoever decides to add some gufuncs (maybe to Numba apparently has an API for defining user-defined gufuncs. If both options are officially supported documented things, then presumably someone will have to waste some time adding a Numba-level wrapper for selecting which behaviour they want, documenting this, etc. Again this is wasted time, given that AFAWK everyone wants the strict behaviour. |
@njsmith So are you happy with cold turkey? |
I'm happy with cold turkey. On Wed, Oct 8, 2014 at 3:10 AM, Charles Harris notifications@github.com
Nathaniel J. Smith |
A gufunc user wannabe has come out of the closet today in StackOverflow: http://stackoverflow.com/questions/26285595/generalized-universal-function-in-numpy |
I don't think the request on stackoverflow is actually coherent with how gufuncs work? I left a comment anyway. @jaimefrio: It sounds like we're at least closer to consensus... are you up for modifying this PR to make it all-strict-all-the-time? |
Yes, I should find some time in the next couple of days: we moved last week, so things are still a little hectic around here. I think I will still see if a deprecation warning can be added, if only to remove it afterwards. |
* Arguments, both input and output, must have at least as many dimensions as the corresponding number of core dimensions. In previous versions, 1's were prepended to the shape as needed. * Core dimensions with same labels must have exactly matching sizes. In previous versions, core dimensions of size 1 would broadcast against other core dimensions with the same label. * All core dimensions must have their size specified by a passed in input or output argument. In previous versions, core dimensions in an output argument that were not specified in an input argument, and whose size could not be inferred from a passed in output argument, would have their size set to 1.
Added euclidean_pdist to umath/umath_tests.c.src. Modified tests to reflect new, stricter gufunc signature behavior.
c3538e1
to
a2267ba
Compare
OK, redid the whole thing: cold turkey it is. Much simpler indeed. Lets see what Travis thinks... |
a = np.arange(8).reshape((4, 2)) | ||
b = np.arange(4).reshape((4, 1)) | ||
assert_array_equal(umt.inner1d(a, b), np.sum(a*b, axis=-1), err_msg=msg) | ||
msg = "extend & broadcast core and loop dimensions" | ||
assert_raises(ValueError, umt.inner1d, a, b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably also have a testcase that would previously invoked the prepend-1 behaviour. I guess inner1d(scalar, scalar)
would work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what the next test (Extend core dimensions) does.
otherwise LGTM On Thu, Oct 16, 2014 at 5:46 PM, Jaime notifications@github.com wrote:
Nathaniel J. Smith |
into core and loop dimensions: | ||
|
||
#. While an input array has a smaller dimensionality than the corresponding | ||
number of core dimensions, 1's are pre-pended to its shape. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prepended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And same for other uses.
a2267ba
to
db04d2a
Compare
Documented the the new behavior in c-api.generalized-ufuncs.rst. Added PyUFunc_FromFuncAndDataAndSignature to c-api.ufunc.rst.
db04d2a
to
528bac1
Compare
I have added your suggestions, plus fixed a couple more places in the docs, and added a note to the release notes. |
@jaimefrio Just a spelling nitpick. I think this is almost ready to go. |
@charris I think I fixed them all... There is a comment of yours about pre-pended vs prepended, but that's on a removed line. |
WIP: gufunc core dimensions should not broadcast
OK, merged. Thanks Jaime. |
No description provided.