From ed1ba54bf3af2ae7a65aa5b4b1dd932cdc7613c7 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:40:14 +0200 Subject: [PATCH 1/2] Reject masked shared random parameters --- src/pyrecest/_backend/_shared_numpy/random.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pyrecest/_backend/_shared_numpy/random.py b/src/pyrecest/_backend/_shared_numpy/random.py index 9257bba756..5938acdc7f 100644 --- a/src/pyrecest/_backend/_shared_numpy/random.py +++ b/src/pyrecest/_backend/_shared_numpy/random.py @@ -84,6 +84,8 @@ def _normalize_size(size): def _validate_uniform_bound(bound, name): + if _contains_masked_value(bound): + raise TypeError(f"{name} must be real numeric") if _contains_boolean_value(bound): raise TypeError(f"{name} must be real numeric, not boolean") try: @@ -118,6 +120,8 @@ def _uniform(low=0.0, high=1.0, size=None): def _validate_normal_parameter(value, name): + if _contains_masked_value(value): + raise TypeError(f"{name} must be real numeric") if _contains_boolean_value(value): raise TypeError(f"{name} must be real numeric, not boolean") try: @@ -150,6 +154,8 @@ def _normal(loc=0.0, scale=1.0, size=None): def _validate_multivariate_normal_parameter(value, name): + if _contains_masked_value(value): + raise TypeError(f"{name} must be real numeric") if _contains_boolean_value(value): raise TypeError(f"{name} must be real numeric, not boolean") try: @@ -246,6 +252,8 @@ def _integer_choice_population_size(a_array): def _validate_choice_probabilities(p, population_size): if p is None: return None + if _contains_masked_value(p): + raise TypeError("p must be real numeric") if _contains_boolean_value(p): raise TypeError("p must be real numeric, not boolean") try: From 3990677377b0dd056448df5cf965fcf32c8b3c66 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:40:31 +0200 Subject: [PATCH 2/2] Add masked random parameter regressions --- ...y_random_masked_distribution_parameters.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/backend/test_numpy_random_masked_distribution_parameters.py diff --git a/tests/backend/test_numpy_random_masked_distribution_parameters.py b/tests/backend/test_numpy_random_masked_distribution_parameters.py new file mode 100644 index 0000000000..13a0252ef5 --- /dev/null +++ b/tests/backend/test_numpy_random_masked_distribution_parameters.py @@ -0,0 +1,89 @@ +import numpy as np +import pytest +from pyrecest._backend.numpy import random + + +@pytest.mark.parametrize( + ("sampler", "message"), + ( + ( + lambda: random.uniform(low=np.ma.array(0.0, mask=True)), + "low must be real numeric", + ), + ( + lambda: random.uniform(high=np.ma.array(1.0, mask=True)), + "high must be real numeric", + ), + ( + lambda: random.normal(loc=np.ma.array(0.0, mask=True)), + "loc must be real numeric", + ), + ( + lambda: random.normal(scale=np.ma.array(1.0, mask=True)), + "scale must be real numeric", + ), + ( + lambda: random.multivariate_normal( + mean=np.ma.array([0.0, 1.0], mask=[False, True]), + cov=np.eye(2), + ), + "mean must be real numeric", + ), + ( + lambda: random.multivariate_normal( + mean=np.zeros(2), + cov=np.ma.array( + np.eye(2), + mask=[[False, False], [False, True]], + ), + ), + "cov must be real numeric", + ), + ( + lambda: random.choice( + 2, + p=np.ma.array([0.25, 0.75], mask=[False, True]), + ), + "p must be real numeric", + ), + ( + lambda: random.choice(2, p=[0.25, np.ma.masked]), + "p must be real numeric", + ), + ), +) +def test_numpy_random_rejects_masked_distribution_parameters(sampler, message): + with pytest.raises(TypeError, match=message): + sampler() + + +def test_numpy_random_accepts_fully_unmasked_distribution_parameters(): + uniform_samples = random.uniform( + low=np.ma.array(0.0, mask=False), + high=np.ma.array(1.0, mask=False), + size=4, + ) + normal_samples = random.normal( + loc=np.ma.array(0.0, mask=False), + scale=np.ma.array(1.0, mask=False), + size=4, + ) + multivariate_samples = random.multivariate_normal( + mean=np.ma.array([0.0, 1.0], mask=False), + cov=np.ma.array(np.eye(2), mask=False), + size=3, + ) + choice_samples = random.choice( + 2, + size=4, + p=np.ma.array([0.25, 0.75], mask=False), + ) + + assert uniform_samples.shape == (4,) + assert normal_samples.shape == (4,) + assert multivariate_samples.shape == (3, 2) + assert choice_samples.shape == (4,) + assert np.isfinite(uniform_samples).all() + assert np.isfinite(normal_samples).all() + assert np.isfinite(multivariate_samples).all() + assert np.isin(choice_samples, [0, 1]).all()