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: Fix bounds checking for random.logseries #22594

Merged
merged 1 commit into from Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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