Skip to content

Commit

Permalink
Merge pull request #22594 from charris/backport-22450
Browse files Browse the repository at this point in the history
BUG: Fix boundschecking for `random.logseries`
  • Loading branch information
charris committed Nov 15, 2022
2 parents 8cededd + e88592e commit 3ca02ce
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 19 deletions.
2 changes: 1 addition & 1 deletion numpy/random/_common.pxd
Expand Up @@ -17,8 +17,8 @@ cdef enum ConstraintType:
CONS_POSITIVE
CONS_POSITIVE_NOT_NAN
CONS_BOUNDED_0_1
CONS_BOUNDED_0_1_NOTNAN
CONS_BOUNDED_GT_0_1
CONS_BOUNDED_LT_0_1
CONS_GT_1
CONS_GTE_1
CONS_POISSON
Expand Down
6 changes: 6 additions & 0 deletions numpy/random/_common.pyx
Expand Up @@ -392,6 +392,9 @@ cdef int check_array_constraint(np.ndarray val, object name, constraint_type con
elif cons == CONS_BOUNDED_GT_0_1:
if not np.all(np.greater(val, 0)) or not np.all(np.less_equal(val, 1)):
raise ValueError("{0} <= 0, {0} > 1 or {0} contains NaNs".format(name))
elif cons == CONS_BOUNDED_LT_0_1:
if not np.all(np.greater_equal(val, 0)) or not np.all(np.less(val, 1)):
raise ValueError("{0} < 0, {0} >= 1 or {0} contains NaNs".format(name))
elif cons == CONS_GT_1:
if not np.all(np.greater(val, 1)):
raise ValueError("{0} <= 1 or {0} contains NaNs".format(name))
Expand Down Expand Up @@ -428,6 +431,9 @@ cdef int check_constraint(double val, object name, constraint_type cons) except
elif cons == CONS_BOUNDED_GT_0_1:
if not val >0 or not val <= 1:
raise ValueError("{0} <= 0, {0} > 1 or {0} contains NaNs".format(name))
elif cons == CONS_BOUNDED_LT_0_1:
if not (val >= 0) or not (val < 1):
raise ValueError("{0} < 0, {0} >= 1 or {0} is NaN".format(name))
elif cons == CONS_GT_1:
if not (val > 1):
raise ValueError("{0} <= 1 or {0} is NaN".format(name))
Expand Down
8 changes: 4 additions & 4 deletions numpy/random/_generator.pyx
Expand Up @@ -25,7 +25,7 @@ from ._pcg64 import PCG64
from numpy.random cimport bitgen_t
from ._common cimport (POISSON_LAM_MAX, CONS_POSITIVE, CONS_NONE,
CONS_NON_NEGATIVE, CONS_BOUNDED_0_1, CONS_BOUNDED_GT_0_1,
CONS_GT_1, CONS_POSITIVE_NOT_NAN, CONS_POISSON,
CONS_BOUNDED_LT_0_1, CONS_GT_1, CONS_POSITIVE_NOT_NAN, CONS_POISSON,
double_fill, cont, kahan_sum, cont_broadcast_3, float_fill, cont_f,
check_array_constraint, check_constraint, disc, discrete_broadcast_iii,
validate_output_shape
Expand Down Expand Up @@ -3437,12 +3437,12 @@ cdef class Generator:
Draw samples from a logarithmic series distribution.

Samples are drawn from a log series distribution with specified
shape parameter, 0 < ``p`` < 1.
shape parameter, 0 <= ``p`` < 1.

Parameters
----------
p : float or array_like of floats
Shape parameter for the distribution. Must be in the range (0, 1).
Shape parameter for the distribution. Must be in the range [0, 1).
size : int or tuple of ints, optional
Output shape. If the given shape is, e.g., ``(m, n, k)``, then
``m * n * k`` samples are drawn. If size is ``None`` (default),
Expand Down Expand Up @@ -3506,7 +3506,7 @@ cdef class Generator:

"""
return disc(&random_logseries, &self._bitgen, size, self.lock, 1, 0,
p, 'p', CONS_BOUNDED_0_1,
p, 'p', CONS_BOUNDED_LT_0_1,
0.0, '', CONS_NONE,
0.0, '', CONS_NONE)

Expand Down
10 changes: 5 additions & 5 deletions numpy/random/mtrand.pyx
Expand Up @@ -19,8 +19,8 @@ from ._bounded_integers cimport (_rand_bool, _rand_int32, _rand_int64,
from ._mt19937 import MT19937 as _MT19937
from numpy.random cimport bitgen_t
from ._common cimport (POISSON_LAM_MAX, CONS_POSITIVE, CONS_NONE,
CONS_NON_NEGATIVE, CONS_BOUNDED_0_1, CONS_BOUNDED_GT_0_1, CONS_GTE_1,
CONS_GT_1, LEGACY_CONS_POISSON,
CONS_NON_NEGATIVE, CONS_BOUNDED_0_1, CONS_BOUNDED_GT_0_1,
CONS_BOUNDED_LT_0_1, CONS_GTE_1, CONS_GT_1, LEGACY_CONS_POISSON,
double_fill, cont, kahan_sum, cont_broadcast_3,
check_array_constraint, check_constraint, disc, discrete_broadcast_iii,
validate_output_shape
Expand Down Expand Up @@ -3895,7 +3895,7 @@ cdef class RandomState:
Draw samples from a logarithmic series distribution.
Samples are drawn from a log series distribution with specified
shape parameter, 0 < ``p`` < 1.
shape parameter, 0 <= ``p`` < 1.
.. note::
New code should use the ``logseries`` method of a ``default_rng()``
Expand All @@ -3904,7 +3904,7 @@ cdef class RandomState:
Parameters
----------
p : float or array_like of floats
Shape parameter for the distribution. Must be in the range (0, 1).
Shape parameter for the distribution. Must be in the range [0, 1).
size : int or tuple of ints, optional
Output shape. If the given shape is, e.g., ``(m, n, k)``, then
``m * n * k`` samples are drawn. If size is ``None`` (default),
Expand Down Expand Up @@ -3969,7 +3969,7 @@ cdef class RandomState:
"""
out = disc(&legacy_logseries, &self._bitgen, size, self.lock, 1, 0,
p, 'p', CONS_BOUNDED_0_1,
p, 'p', CONS_BOUNDED_LT_0_1,
0.0, '', CONS_NONE,
0.0, '', CONS_NONE)
# Match historical output type
Expand Down
20 changes: 16 additions & 4 deletions numpy/random/tests/test_generator_mt19937.py
Expand Up @@ -1363,10 +1363,22 @@ def test_logseries(self):
[5, 1]])
assert_array_equal(actual, desired)

def test_logseries_exceptions(self):
with np.errstate(invalid='ignore'):
assert_raises(ValueError, random.logseries, np.nan)
assert_raises(ValueError, random.logseries, [np.nan] * 10)
def test_logseries_zero(self):
random = Generator(MT19937(self.seed))
assert random.logseries(0) == 1

@pytest.mark.parametrize("value", [np.nextafter(0., -1), 1., np.nan, 5.])
def test_logseries_exceptions(self, value):
random = Generator(MT19937(self.seed))
with np.errstate(invalid="ignore"):
with pytest.raises(ValueError):
random.logseries(value)
with pytest.raises(ValueError):
# contiguous path:
random.logseries(np.array([value] * 10))
with pytest.raises(ValueError):
# non-contiguous path:
random.logseries(np.array([value] * 10)[::2])

def test_multinomial(self):
random = Generator(MT19937(self.seed))
Expand Down
19 changes: 14 additions & 5 deletions numpy/random/tests/test_randomstate.py
Expand Up @@ -942,11 +942,20 @@ def test_logseries(self):
[3, 6]])
assert_array_equal(actual, desired)

def test_logseries_exceptions(self):
with suppress_warnings() as sup:
sup.record(RuntimeWarning)
assert_raises(ValueError, random.logseries, np.nan)
assert_raises(ValueError, random.logseries, [np.nan] * 10)
def test_logseries_zero(self):
assert random.logseries(0) == 1

@pytest.mark.parametrize("value", [np.nextafter(0., -1), 1., np.nan, 5.])
def test_logseries_exceptions(self, value):
with np.errstate(invalid="ignore"):
with pytest.raises(ValueError):
random.logseries(value)
with pytest.raises(ValueError):
# contiguous path:
random.logseries(np.array([value] * 10))
with pytest.raises(ValueError):
# non-contiguous path:
random.logseries(np.array([value] * 10)[::2])

def test_multinomial(self):
random.seed(self.seed)
Expand Down

0 comments on commit 3ca02ce

Please sign in to comment.