8000 BUG: random: Generator.integers(2**32) always returned 0. by WarrenWeckesser · Pull Request #16076 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

BUG: random: Generator.integers(2**32) always returned 0. #16076

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

Merged
merged 4 commits into from
Apr 27, 2020

Conversation

WarrenWeckesser
Copy link
Member
@WarrenWeckesser WarrenWeckesser commented Apr 25, 2020

When the input to Generator.integers was 2**32, the value 2**32-1
was being passed as the rng argument to the 32-bit Lemire method,
but that method requires rng be strictly less than 2**32-1.

The fix was to handle 2**32-1 by calling next_uint32 directly.
This also works for the legacy code without changing the stream
of random integers from randint.

Closes gh-16066.

@seberg
Copy link
Member
seberg commented Apr 25, 2020

Any chance of the same issue for 2**64 (not through integers maybe)?

EDIT: Oh, sorry, I see the 64 bit version has this path already. A test could make sense but I assume it already exists.

@seberg
Copy link
Member
seberg commented Apr 25, 2020

@bashtage would you have time to review this to make sure its good?

@seberg seberg added the 09 - Backport-Candidate PRs tagged should be backported label Apr 25, 2020
Copy link
Member
@seberg seberg left a comment

Choose a reason for hiding this comment

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

LGTM, I think fine to put in as is. It seems to me the test should include also drawing a single sample to cover both paths, though?
Otherwise, will leave it in case Kevin wants to have a look, but lets not hesitate to merge to get 1.18.4 out.

@WarrenWeckesser
Copy link
Member Author

@seberg wrote

It seems to me the test should include also drawing a single sample to cover both paths, though?

The test in there now is a crude regression test. I'll replace that with a repeatability test for 2**32-1, 2**32 and 2**32+1.

@seberg
Copy link
Member
seberg commented Apr 26, 2020

Right, just need to also cover the path where only a single number is drawn then. The approach is good in any case, I was wondering whether it makes sense to put an assert for this into the Lemirs method. We could just do that, although I doubt it helps much with flushing these issues out.

When the input to Generator.integers was 2**32, the value 2**32-1
was being passed as the `rng` argument to the 32-bit Lemire method,
but that method requires `rng` be strictly less then 2**32-1.

The fix was to handle 2**32-1 by calling next_uint32 directly.
This also works for the legacy code without changing the stream
of random integers from `randint`.

Closes numpygh-16066.
Assert that an invalid value (2**n-1 for n = 8, 16, 32, 64) has not
been passed to the Lemire function.
@WarrenWeckesser
Copy link
Member Author

It took a few tries, but the Travis-CI tests on the s390x platform finally ran successfully.

I've changed the unit test to a set of repeatability tests for the values 2**32-1, 2**32 and 2**32+1. For each value, a test is run with size=7 and size=None.

I also added assert calls in the C implementations of the Lemire method to check for the invalid value.

Copy link
Contributor
@bashtage bashtage left a comment

Choose a reason for hiding this comment

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

LGTM, modulo checking that the stream in RandomState is not affected.

for (i = 0; i < cnt; i++) {
out[i] = off + buffered_bounded_masked_uint32(bitgen_state, rng, mask,
&bcnt, &buf);
out[i] = off + (uint64_t) next_uint32(bitgen_state);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this code used in RandomState, and does this preserve the stream guarantee? RS uses only masked, so intercepting before this might have been called could can an issue.

Would it be safest to added a test in RandomState too with the same end points?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is tested by the test that was added in gh-14501:

@pytest.mark.skipif(np.iinfo('l').max < 2**32,

* call next_uint32 directly. This also works when use_masked is True,
* so we handle both cases here.
*/
return off + (uint64_t) next_uint32(bitgen_state);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the same would apply here as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this needs a test. When calling randint with scalar values of low and high, this path is not reached, even when size is None. It turns out that the function random_bounded_uint64 is called by the code that handles broadcasting for random.randint. I'll add a repeatability test for that.

Add repeatability tests for when the range of the integers is `2**32`
(and `2**32 +/- 1` for good measure) with broadcasting.  The underlying
functions called by Generator.integers and random.randint when the
inputs are broadcast are different than when the inputs are scalars.
@seberg seberg added this to the 1.18.4 release milestone Apr 27, 2020
Copy link
Contributor
@bashtage bashtage left a comment

Choose a reason for hiding this comment

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

Extra test was all I noticed. It has been added.

@seberg
Copy link
Member
seberg commented Apr 27, 2020

@WarrenWeckesser cool, thanks for the nice tests. Unfortunately due to 32bit longs, the tests are failing on windows (and presumably 32bit linux).

@WarrenWeckesser
Copy link
Member Author

@seberg, yup, just noticed that. Looking into it.

@WarrenWeckesser
Copy link
Member Author

For the new randint test, I used the same skipif decorator as in #14501. Tests are all green now.

@seberg
Copy link
Member
seberg commented Apr 27, 2020

Seems good to just skip. Thanks for tracking it down and fixing it quickly Warren!

@seberg seberg merged commit 92b880f into numpy:master Apr 27, 2020
@WarrenWeckesser WarrenWeckesser deleted the fix-gh-16066 branch April 27, 2020 19:14
@charris charris removed the 09 - Backport-Candidate PRs tagged should be backported label Apr 27, 2020
@charris
Copy link
Member
charris commented Apr 27, 2020

AFAIK, RandomState does not use the Lemire method, only the mask method. For repeatability tests, I used fairly long streams and hashed them, then checked the hash. Takes up a lot less room.

Repeatability is not guaranteed for the Generator methods. At some point we should have correctness checks, maybe as a separate set of tests.

@WarrenWeckesser
Copy link
Member Author

@charris wrote

Repeatability is not guaranteed for the Generator methods.

My understanding is that we have repeatability tests for the Generator methods so we can detect a change, and create a release note about the change if turns out that the change is unavoidable.

@WarrenWeckesser
Copy link
Member Author

At some point we should have correctness checks, maybe as a separate set of tests.

Agreed. The issue gh-15911 that you created looks like a good place to discuss this.

@WarrenWeckesser WarrenWeckesser added this to the 1.19.0 release milestone May 16, 2020
@iyanmv
Copy link
iyanmv commented Nov 18, 2021

Was this ever merged to 1.17.x? So, correct me if I'm wrong, but the first version of NumPy that can be used without issues gh-16066 and gh-14774 is 1.18.4, right?

@bashtage
Copy link
Contributor

The last 17 branch was released before this PR was merged, so it was never in the 17 branch. It was backported to 18 and in 19.0.

Sorry, something went wrong.

@iyanmv
Copy link
iyanmv commented Nov 18, 2021

Okay, so I guess a bump in requirements-min.txt is needed in my case.

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.

default_rng.integers(2**32) always return 0
5 participants
0