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

BUG: np.resize negative shape and subclasses edge case fixes #15037

Merged
merged 1 commit into from
May 26, 2020
Merged

BUG: np.resize negative shape and subclasses edge case fixes #15037

merged 1 commit into from
May 26, 2020

Conversation

tirthasheshpatel
Copy link
Contributor

Fixes: #15030

@eric-wieser
Copy link
Member

I think you should be raising an error when the size is negative, not just forcing it to be positive

@tirthasheshpatel
Copy link
Contributor Author

@eric-wieser
Done!

@@ -48,6 +48,14 @@ def test_reshape_from_zero(self):
assert_array_equal(Ar, np.zeros((2, 1), Ar.dtype))
assert_equal(A.dtype, Ar.dtype)

def test_negativeresize(self):
A = np.arange(0, 10, dtype=np.float32)
new_shape = (10,-1)
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 for (-10, -1) too, which should also fail?

(hint: It won't fail, you need to adjust your condition above)

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 think this should be resolved now

new_shape = (10,-1)
err_msg = (r"new_shape can't have negative values."
r" found: new_shape=(10,-1)")
with pytest.raises(ValueError, match=err_msg):
Copy link
Member

Choose a reason for hiding this comment

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

match="negative" would probably be enough here - matching the full message makes the test overly brittle to wording changes.

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 dont know why the tests are failing with the current fix! Can you please guide me through it? Thanks for your replies!

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

There's a bug in the error handling, opened gh-15039

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohk! Thanks!

@eric-wieser
Copy link
Member

Close / reopen to rerun tests now that #15039 is in

@eric-wieser
Copy link
Member

@seberg
Copy link
Member

seberg commented Dec 3, 2019

The reason is probably that any is actually np.any here, which misbehaves for generators. It should be using __builtin__.any. The best solution is probably to avoid importing the numpy any at all.

EDIT: Oh, any is actually defined in this file. Maybe spelling it __builtin__.any is actually fine...

@seberg
Copy link
Member

seberg commented Dec 3, 2019

I pushed a commit trying that (some hopefully slight rewording/style fixes).

@tirthasheshpatel
Copy link
Contributor Author

I am so sorry to add some bad commit from my work on another issue!
@seberg can you please add the previous commit again!

@seberg
Copy link
Member

seberg commented Dec 23, 2019

Sorry, I had lost track of this. Forced push the old version here. But I think we may try one from the discussion I had with Eric. Do you want to have a look, otherwise please ping me so we can maybe just fix this up.

@eric-wieser
Copy link
Member

@seberg want to finish this up? Perhaps the implementation should become:

ret = np.zeros_like(a, shape=new_shape, order="C")
ret.flat[:] = a  # if a.size == 0 this is a no-op, hence the `zeros` above
return ret

@seberg
Copy link
Member

seberg commented Jan 16, 2020

So... turned out the .flat solution does not work because MaskedArray.flat is probably utterly broken. In an attempt to not change the annoying subclassing semantics, I just went back to the first solution (except avoiding any, by iterating manually because it makes no real difference anyway).

The concatenate(..., out=...) idea cannot work since the new array is smaller (plus I do not trust these things with strange subclasses for now).

There is an additional bug fix, since it should be np.zeros_like() and not np.zeros...

Comment on lines 1428 to 1429
extra = new_size % a.size
repeats = (new_size - extra) // a.size
Copy link
Member

@eric-wieser eric-wieser Jan 16, 2020

Choose a reason for hiding this comment

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

Shorter and perhaps faster as

repeats, extra = divmod(new_size, a.size)

edit: actually, I think what you have here is wrong - shouldn't repeats round up, not down? That can be spelt as:

repeats, extra = divmod(newsize,-a.size)
repeats = -repeats

or perhaps more clearly

repeats = -(-newsize // a.size)  # ceil division
a = concatenate((a,) * repeats)
a = a[:newsize]

Copy link
Member

Choose a reason for hiding this comment

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

There is no down rounding here, since the result is always an exact integer (the // only makes sure it remains an int)? But yes, divmod is better, somehow forgot it is a python function/operator...

Copy link
Member

Choose a reason for hiding this comment

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

There is rounding down, you did it via the subtraction.

As I read it, this will make np.arange(5).resize(7) give an array of the wrong size - your division will give 1, and then extra will be 2 so you'll return an array of length 3.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, yeah, I guess I would have thought such a big error gets found by the tests :/

Copy link
Member

Choose a reason for hiding this comment

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

Jeesh... emberassing. I just opted for duplicating the concatenate line to keep it stupid, open for other things, but...

Copy link
Member

Choose a reason for hiding this comment

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

Is there a test for np.arange(5).resize(7)?

Copy link
Member

Choose a reason for hiding this comment

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

There is now...

Copy link
Member

Choose a reason for hiding this comment

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

where?

@seberg seberg changed the title FIX: negative shape in np.resize is now handled BUG: np.resize negative shape and subclasses edge case fixes Jan 16, 2020

def test_subclass(self):
class MyArray(np.ndarray):
__array_priority__ = 1.
Copy link
Member

Choose a reason for hiding this comment

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

What happens without this member?

Copy link
Member

Choose a reason for hiding this comment

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

concatenate seems to lose the subclass info. That could actually be a bug, since all inputs are MyArray.

Copy link
Member

Choose a reason for hiding this comment

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

Doesn't concatenate always lose subclass info?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, no, the test would fail if it did :).

When a negative entry is passed into `np.resize` shape, an error will
now be raised reliably. Previously the `-1` semantics of reshape allowed
edge cases to pass and return incorrect results.
In the corner case of an empty result shape or empty input shape, the
result type will now be preserved.
@Qiyu8 Qiyu8 requested review from seberg and eric-wieser May 25, 2020 08:09
@seberg
Copy link
Member

seberg commented May 26, 2020

I think this was ready, I am a bit hesitant to merge though, since I was the one who pushed the last changed.

extra = total_size % Na
new_size = 1
for dim_length in new_shape:
new_size *= dim_length
Copy link
Member

Choose a reason for hiding this comment

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

Could put this below the error check for a marginal speed boost in the error case, but doesn't really matter.

Copy link
Member

Choose a reason for hiding this comment

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

Doubt its nicer to read, and I refuse to optimize error-cases without good reason :). Thanks for another pass Eric lets put it in.

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.

BUG: numpy.resize returns wrong size when using -1 syntax for axis length.
5 participants