From 2623882775a6c9e9d4fbd0e632ea7003b266fe32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marein=20K=C3=B6nings?= Date: Sun, 5 Mar 2017 16:32:34 +0100 Subject: [PATCH 1/3] ENH: allow size=0 in random.randint and random.choice --- doc/release/1.13.0-notes.rst | 7 +++++++ numpy/random/mtrand/mtrand.pyx | 4 +--- numpy/random/mtrand/randint_helpers.pxi.in | 5 ++++- numpy/random/tests/test_random.py | 6 ++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/release/1.13.0-notes.rst b/doc/release/1.13.0-notes.rst index 5f8c40cd2727..38def843daf4 100644 --- a/doc/release/1.13.0-notes.rst +++ b/doc/release/1.13.0-notes.rst @@ -208,6 +208,13 @@ array, in the same way that ``sort`` already did. Additionally, the Note that this argument is not added at the end, so breaks any code that passed ``fill_value`` as a positional argument. +``randint`` and ``choice`` now work on empty distributions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Even when no elements needed to be drawn, ``np.random.randint`` and +``np.random.choice`` raised an error when the arguments described an empty +distribution. This has been fixed so that e.g. +``np.random.choice([],0) == np.array([],dtype=float64)``. + Changes ======= diff --git a/numpy/random/mtrand/mtrand.pyx b/numpy/random/mtrand/mtrand.pyx index bf3a385a97a7..10f786d7cba5 100644 --- a/numpy/random/mtrand/mtrand.pyx +++ b/numpy/random/mtrand/mtrand.pyx @@ -973,7 +973,7 @@ cdef class RandomState: raise ValueError("low is out of bounds for %s" % (key,)) if high > highbnd: raise ValueError("high is out of bounds for %s" % (key,)) - if low >= high: + if low >= high and np.prod(size) != 0: raise ValueError("low >= high") with self.lock: @@ -1106,8 +1106,6 @@ cdef class RandomState: raise ValueError("a must be 1-dimensional") else: pop_size = a.shape[0] - if pop_size is 0: - raise ValueError("a must be non-empty") if p is not None: d = len(p) diff --git a/numpy/random/mtrand/randint_helpers.pxi.in b/numpy/random/mtrand/randint_helpers.pxi.in index 4bd7cd35614e..7e0a96357a6a 100644 --- a/numpy/random/mtrand/randint_helpers.pxi.in +++ b/numpy/random/mtrand/randint_helpers.pxi.in @@ -60,16 +60,19 @@ def _rand_{{npy_dt}}(low, high, size, rngstate): cdef npy_intp cnt cdef rk_state *state = PyCapsule_GetPointer(rngstate, NULL) - rng = (high - low) off = (low) if size is None: + rng = (high - low) rk_random_{{npy_udt}}(off, rng, 1, &buf, state) return np.{{np_dt}}(buf) + elif np.prod(size) == 0: + return np.empty(size, np.{{np_dt}}) else: array = np.empty(size, np.{{np_dt}}) cnt = PyArray_SIZE(array) array_data = PyArray_DATA(array) + rng = (high - low) with nogil: rk_random_{{npy_udt}}(off, rng, cnt, array_data, state) return array diff --git a/numpy/random/tests/test_random.py b/numpy/random/tests/test_random.py index e4c58e2bd430..c05e14f09670 100644 --- a/numpy/random/tests/test_random.py +++ b/numpy/random/tests/test_random.py @@ -390,6 +390,12 @@ def test_choice_return_shape(self): assert_equal(np.random.choice(6, s, replace=False, p=p).shape, s) assert_equal(np.random.choice(np.arange(6), s, replace=True).shape, s) + # Check zero-size + assert_equal(np.random.randint(0,0,(3,0,4)).shape, (3,0,4)) + assert_equal(np.random.randint(0,-10,0).shape, (0,)) + assert_equal(np.random.choice([],(0,)).shape, (0,)) + assert_equal(np.random.choice(['a', 'b'], size=(3, 0, 4)).shape, (3, 0, 4)) + def test_bytes(self): np.random.seed(self.seed) actual = np.random.bytes(10) From 4f767930cc5ce0b6221346c9ab19dcaa41906d27 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Tue, 7 Mar 2017 22:41:44 +0000 Subject: [PATCH 2/3] BUG: Avoid unsigned overflow in subtraction --- numpy/random/mtrand/mtrand.pyx | 2 +- numpy/random/mtrand/randint_helpers.pxi.in | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/numpy/random/mtrand/mtrand.pyx b/numpy/random/mtrand/mtrand.pyx index 10f786d7cba5..f2f704fa4906 100644 --- a/numpy/random/mtrand/mtrand.pyx +++ b/numpy/random/mtrand/mtrand.pyx @@ -22,8 +22,8 @@ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. include "Python.pxi" -include "randint_helpers.pxi" include "numpy.pxd" +include "randint_helpers.pxi" include "cpython/pycapsule.pxd" from libc cimport string diff --git a/numpy/random/mtrand/randint_helpers.pxi.in b/numpy/random/mtrand/randint_helpers.pxi.in index 7e0a96357a6a..894a25167f5f 100644 --- a/numpy/random/mtrand/randint_helpers.pxi.in +++ b/numpy/random/mtrand/randint_helpers.pxi.in @@ -23,7 +23,7 @@ def get_dispatch(dtypes): {{for npy_dt, npy_udt, np_dt in get_dispatch(dtypes)}} -def _rand_{{npy_dt}}(low, high, size, rngstate): +def _rand_{{npy_dt}}(npy_{{npy_dt}} low, npy_{{npy_dt}} high, size, rngstate): """ _rand_{{npy_dt}}(low, high, size, rngstate) @@ -60,19 +60,16 @@ def _rand_{{npy_dt}}(low, high, size, rngstate): cdef npy_intp cnt cdef rk_state *state = PyCapsule_GetPointer(rngstate, NULL) - off = (low) + off = (low) + rng = (high) - (low) if size is None: - rng = (high - low) rk_random_{{npy_udt}}(off, rng, 1, &buf, state) return np.{{np_dt}}(buf) - elif np.prod(size) == 0: - return np.empty(size, np.{{np_dt}}) else: array = np.empty(size, np.{{np_dt}}) cnt = PyArray_SIZE(array) array_data = PyArray_DATA(array) - rng = (high - low) with nogil: rk_random_{{npy_udt}}(off, rng, cnt, array_data, state) return array From 5d5545eb6c51970e5b8cb87c00768a11d95f6b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marein=20K=C3=B6nings?= Date: Wed, 8 Mar 2017 16:07:58 +0100 Subject: [PATCH 3/3] Improve error messages, add tests --- numpy/random/mtrand/mtrand.pyx | 8 +++++--- numpy/random/tests/test_random.py | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/numpy/random/mtrand/mtrand.pyx b/numpy/random/mtrand/mtrand.pyx index f2f704fa4906..6460acf8218d 100644 --- a/numpy/random/mtrand/mtrand.pyx +++ b/numpy/random/mtrand/mtrand.pyx @@ -974,7 +974,7 @@ cdef class RandomState: if high > highbnd: raise ValueError("high is out of bounds for %s" % (key,)) if low >= high and np.prod(size) != 0: - raise ValueError("low >= high") + raise ValueError("Range cannot be empty (low >= high) unless no samples are taken") with self.lock: ret = randfunc(low, high - 1, size, self.state_address) @@ -1100,12 +1100,14 @@ cdef class RandomState: pop_size = operator.index(a.item()) except TypeError: raise ValueError("a must be 1-dimensional or an integer") - if pop_size <= 0: - raise ValueError("a must be greater than 0") + if pop_size <= 0 and np.prod(size) != 0: + raise ValueError("a must be greater than 0 unless no samples are taken") elif a.ndim != 1: raise ValueError("a must be 1-dimensional") else: pop_size = a.shape[0] + if pop_size is 0 and np.prod(size) != 0: + raise ValueError("a cannot be empty unless no samples are taken") if p is not None: d = len(p) diff --git a/numpy/random/tests/test_random.py b/numpy/random/tests/test_random.py index c05e14f09670..16dd9102b2c6 100644 --- a/numpy/random/tests/test_random.py +++ b/numpy/random/tests/test_random.py @@ -393,8 +393,10 @@ def test_choice_return_shape(self): # Check zero-size assert_equal(np.random.randint(0,0,(3,0,4)).shape, (3,0,4)) assert_equal(np.random.randint(0,-10,0).shape, (0,)) + assert_equal(np.random.choice(0,0).shape, (0,)) assert_equal(np.random.choice([],(0,)).shape, (0,)) assert_equal(np.random.choice(['a', 'b'], size=(3, 0, 4)).shape, (3, 0, 4)) + assert_raises(ValueError, np.random.choice, [], 10) def test_bytes(self): np.random.seed(self.seed)