-
-
Notifications
You must be signed in to change notification settings - Fork 11.9k
ENH: Adding a count parameter to np.unpackbits #10855
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
Conversation
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.
Added the code comment so I could add the PR comment. Is there a reason we are not using memmove here?
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.
There has got to be a better way than writing my own 2-bit converter, but oh well...
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.
I think PyArray_SizeConverter might be useful, but it probably belongs in a separate MAINT PR, since it has applications elsewhere. I think there's already a function you can call on a PyObject * to convert to an npy_intp, which would be better to just use in the scope of this PR.
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.
Thanks. Could you point me to that function? I wasn't able to find something that matched. PyArray_intpConverter seems to do something entirely different.
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.
BTW, I've moved this out of WIP status since all the tests passed in my local environment.
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.
Just use PyArray_PyIntAsIntp, which takes a PyObject * and returns a npy_intp. A converter might be nice, but it's not in scope for this PR.
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.
Why is it out of scope? I would end up writing the same bit of code anyway, and stashing it where it wouldn't be accessible...
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.
If PyArray_SizeConverter is useful, it's useful with or without this patch. Conversely, this patch is implementable with or without that converter. There's no reason to bundle the changes together into one review.
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.
My main problem with PyArray_PyIntAsIntp is that it does not allow None. What if I defined the converter locally, without adding it to any public APIs, and later submitted a PR for it if someone else wanted it?
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.
Making it a public API would certainly make more sense in a separate review.
Also, you could just use:
npy_intp count = 0;
if (count_obj != Py_None) {
count = PyArray_PyIntAsIntp(count_obj);
if (error_converting(count)) {
goto fail;
}
}
I think I like the idea of a similar converter, but I'd like to see it:
- In a separate PR
- With examples elsewhere in numpy showing it is more widely useful
We don't want to add something to the public API without being sure we chose the right definition. As you observe, PyArray_intpConverter doesn't do what you hoped it would.
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.
Gotcha. Sorry for the slowness.
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.
Please let me know if something like this is defined elsewhere...
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.
I'll be totally honest, I have no idea what I did here or why. The error messages made it pretty clear that the indices need to be unique, but I have no idea why or what they do. Should I have just set this one to 304 (the next available number)? If so, why aren't they all just being generated?
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.
These numbers are not allowed to change, because that will break ABI compatibility for code compiled against previous versions of numpy - so your new number must go at the end,
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.
Definitely will fix then.
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.
What do the numbers actually do?
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.
They are indices in a table of function pointers, which are used to export the functions to other python extension modules.
e385035 to
4931d2f
Compare
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.
This bit really needs some review. Is there a reason memcpy wasn't used for this before? If not, did I use the correct sizeof?
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.
Is there any reason that memcpy should be used here? It's less safe, and the loop should optimize to it anyway.
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.
I was just messing around. I'll go ahead and revert if you think that is better.
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.
If the type of outdims is wrong, this does the wrong thing in all cases, where the previous code would warn and only fail in overflow cases.
Since it's not part of this new feature, and not a clear improvement to me, I'd prefer it not be part of an ENH PR.
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.
The goal was to see if I could get rid of i.
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.
* 8 would be much clearer here
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.
Strangely enough, I decided to keep the existing convention for this bit. I rather prefer << 3 for some reason.
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.
Why did this move? Better to leave things in the narrowest scope possible
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.
Yes. Forgot to move it back when I realized I could optimize the stride=1 loop without using the mask. It's going back ASAP.
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.
nit: space after for
This wastes at most 7 bytes, and incurs no additional copies over your solution. Once you flatten an array, slicing followed by reshaping is O(1). |
|
|
|
But yes, this is largely a convenience, just not purely so. I think that in an abstract sense, there is value to making pack/unpack be completely invertible. |
|
I don't think your converter default is correct - if I pass |
|
I was a bit conflicted about that too. Will change zero to give empty array. That makes the converter pointless, which is actually nice. |
|
@eric-wieser. I belive that I've covered all your requests at this point (including the |
|
|
|
Did I actually cause the Travis CI failure? Or is is something else? |
|
Yes, its your code pretty sure. Might want to run it in valgrind or so to find the issue quickly. Since it is in the relaxed strides test, maybe you are using a stride somewhere and assuming contiguity? (i.e. assuming that EDIT: PS: would it make sense to be make this/allow a bit mask as well? |
|
The bug turned out to be a bit more subtle than just assuming things about the strides. Basically, I forgot to check if there is a tail in the case where |
|
@eric-wieser @seberg In addition to fixing the last lingering bug, I've made a couple of modifications that you may not see as "enhancements". I would be happy to remove either of the last two commits from this PR if you have a reason not to like them. |
|
@seberg I am not sure I understand your PS:
Are you referring to the output dtype or inputting some sort of mask to determine which elements to unpack? The former would be easy: allowing any integer type or boolean for the unpacked result seems reasonable. Might even be easy with just a stride adjustment and an offset to determine if the output is big or little endian. The latter seems like overkill for this PR. That being said, I suspect you had neither of those options in mind... |
|
Why is short-circuiting for size-0 worthwhile? You're making a case that almost never happens marginally faster, at the expense of a marginal slowdown for every other case, and increased complexity to the reader. I'd prefer it without that case.
|
numpy/add_newdocs.py
Outdated
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.
Not sure the negative number is a useful feature here
numpy/add_newdocs.py
Outdated
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.
Padding with zeros would be more useful here, I think?
|
@eric-wieser. I agree with your comments (and made the appropriate fixes), except the negative count. I wanted it to be consistent with the usual indexing scheme, where negatives have the same meaning. |
174852a to
8aedcc0
Compare
8aedcc0 to
61b2dc7
Compare
|
I believe that this PR is ready to go now. I had to compute the threading threshold separately in order to avoid divide-by-zero errors. I hope that I did it in a way that you can agree is fairly painless. |
61b2dc7 to
4b53d4e
Compare
|
Sorry this has been hanging a bit, was a bit busy and sick. I think I will make another quick pass over soon, but should be ready (although I am not sure I read the tests very carefully before). There is still a compiler warning that needs fixing for the tests to pass currently. |
|
@seberg Thanks for clarifying that. I have been sick and busy myself, so gave up quickly on searching for the bug in the code (that I couldn't seem to reproduce). Good to know it's just a casting thing. I'll have that fixed ASAP. |
4b53d4e to
86b7c27
Compare
|
@seberg ASAP wasn't S at all, but it looks like everything is OK now. |
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.
@seberg. As you can see here, myarray was never an option to begin with. The documented keyword name never actually what the function was parsing, and the name being parsed was an illegal name. Although I suppose you could do
kw = {'in': ...}
unpackbits(**kw)
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.
Fair enough. a is better anyway. I wonder if we should at some point work on unifying most of these names (basically a backward incompatible change that would in practice probably hit almost no-one).
86b7c27 to
b32a818
Compare
|
@seberg I think this is ready to go now. |
|
Yes, looks ready. I will put it in a day or two unless something comes up. @mattip this will probably cause merge conflicts with your |
numpy/core/multiarray.py
Outdated
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.
Single quotes are a shortcut for :py:value and are meant to be linked to python documentation. When creating the link fails, the text is rendered in italics. Since the intention here is for code formatting, they should all be two quotes. You can see the difference in the way the default axis argument None used to be rendered here vs this PR here
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.
@mattip. Most of the docs I see have single quotes. I prefer the aesthetic of the italicized version to the code formatted one, and technically None is a python object. However, I don't care much one way or the other, so will fix as you request ASAP.
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.
It is strange that with single quotes, it wouldn't link to the python documentation of None already. Indeed, that seems a bug...
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.
I think I slightly prefer the double backticks for None (there is no point in linking it to the python docs anyway).
https://docs.scipy.org/doc/numpy/docs/howto_document.html has some examples using single ticks for the kwargs, and actually nothing for None. Could be nice to just decide for once, not sure I care though.
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.
I'll grep and count both versions in the existing code. My intuition tells me that single ticks dominate, but I'm not sure. Majority rules? :)
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.
None is just an example here. There are other single-ticked words in the documentation change: a, count, index,
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.
Yeah, although for a, count and axis at least, they are arguments, and the howto document examples use single backticks for those. However, while I would guess a lot of people somewhat use that document, I doubt it is applied consistently with respect to these examples in any case.
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.
Let's call it a niggling nit and ignore it, is this ready for merge otherwise?
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.
There were 145 occurrences of None with single quotes and 147 occurrences with double quotes until this PR came along. I will change back to double. Sorry for the holdup.
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.
@mattip If you want me to go through and normalize all the other occurences, I can do that fairly quickly, since I have the full list and a script that can do it right in front of me.
|
@seberg is there more to do here? |
Tests are included. Couple of minor fixes: - Fixed packbits/unpackbits docs to reflect proper keyword names - Added .pytest_cache to .gitignore
b32a818 to
df00dbf
Compare
|
No, don't think so. Thanks @madphysicist, sorry it took so long. Thanks Matti for the reminder. Going to wait for the test build, were there any changes in that last push? |
|
@seberg Nothing besides some backticks around None and a rebase onto the latest master branch. |
|
@seberg Looks ready to go |
|
OK, thanks @madphysicist and everyone! |
This PR addresses the fact that the recommended method for truncating undesired unpacked elements is currently to reshape and subset (e.g., this SO post). This seems wasteful, especially for large arrays. Adding a
countargument allows the unpacking to use only the desired number of bits, requiring no extra memory.The suggested parameter is pretty straightforward: it will be the final size of the output array along the desired axis. It will error out if you ask for more bits than there are in the data. Negative values indicate how many to trim (so for example, when unpacking a mask of
widthbits from a regularpackbitsinvocation, use-(width % 8)), and None is the existing default behavior (use everything).There is also a fix to the existing docs, which do not accurately reflect the name of the first parameter to
[un]packbitscorrectly.