Skip to content

Commit

Permalink
improve random bitgenerator code coverage (#990)
Browse files Browse the repository at this point in the history
* improve random bitgenerator code coverage

* add error description to zipf failure

* Replace misused assert in generator with appropriate exceptions
  • Loading branch information
yimoj authored Jul 20, 2023
1 parent 1d62db3 commit 46862c4
Show file tree
Hide file tree
Showing 7 changed files with 357 additions and 11 deletions.
12 changes: 9 additions & 3 deletions cunumeric/random/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,15 @@ def random(
out: Union[ndarray, None] = None,
) -> ndarray:
if out is not None:
if size is not None:
assert out.shape == size
assert out.dtype == dtype
if size is not None and out.shape != size:
raise ValueError(
"size must match out.shape when used together"
)
if out.dtype != dtype:
raise TypeError(
"Supplied output array has the wrong type. "
"Expected {}, got {}".format(dtype, out.dtype)
)
return self.bit_generator.random(size, dtype, out)

def rayleigh(
Expand Down
102 changes: 98 additions & 4 deletions tests/integration/test_random_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import sys

import numpy as np
Expand All @@ -20,6 +21,7 @@

import cunumeric as num

LEGATE_TEST = os.environ.get("LEGATE_TEST", None) == "1"
if sys.platform == "darwin":
pytestmark = pytest.mark.skip()
BITGENERATOR_ARGS = []
Expand Down Expand Up @@ -81,7 +83,12 @@ def test_vonmises_float64(t):


@pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str)
def test_hypergeometric(t):
@pytest.mark.parametrize(
"ngood, nbad, nsample",
((60, 440, 200), (20.0, 77, 1), ((3, 5, 7), 6, 22)),
ids=str,
)
def test_hypergeometric(t, ngood, nbad, nsample):
bitgen = t(seed=42)
gen = num.random.Generator(bitgen)
N = 500
Expand Down Expand Up @@ -110,12 +117,24 @@ def test_geometric(t):


@pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str)
def test_zipf(t):
@pytest.mark.parametrize(
"s",
(
7.5,
pytest.param(
(1.2, 3.1415),
marks=pytest.mark.xfail,
# NumPy returns 1-dim array
# cuNumeric raises TypeError: float() argument must be a string
# or a real number, not 'tuple'
),
),
ids=str,
)
def test_zipf(t, s):
bitgen = t(seed=42)
gen = num.random.Generator(bitgen)
s = 7.5
a = gen.zipf(a=s, size=(1024 * 1024,), dtype=np.uint32)
a = np.random.zipf(s, 1024 * 1024)
ref_a = np.random.zipf(s, 1024 * 1024)
theo_mean = np.average(ref_a)
theo_std = np.std(ref_a)
Expand Down Expand Up @@ -173,6 +192,81 @@ def test_negative_binomial(t):
assert_distribution(a, theo_mean, theo_std)


FUNC_ARGS = (
("binomial", (15, 0.666)),
("negative_binomial", (15, 0.666)),
("geometric", (0.707,)),
("hypergeometric", (60, 440, 200)),
("standard_t", (3.1415,)),
("vonmises", (1.414, 3.1415)),
("wald", (1.414, 3.1415)),
("zipf", (7.5,)),
)


@pytest.mark.parametrize("func, args", FUNC_ARGS, ids=str)
@pytest.mark.parametrize("size", ((2048 * 2048), (4096,), 25535), ids=str)
def test_random_sizes(func, args, size):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
a_np = getattr(gen_np, func)(*args, size=size)
a_num = getattr(gen_num, func)(*args, size=size)
assert a_np.shape == a_num.shape


@pytest.mark.xfail
@pytest.mark.parametrize("func, args", FUNC_ARGS, ids=str)
def test_random_size_none(func, args):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
a_np = getattr(gen_np, func)(*args, size=None)
a_num = getattr(gen_num, func)(*args, size=None)
# cuNumeric returns singleton array
# NumPy returns scalar
assert np.ndim(a_np) == np.ndim(a_num)


class TestRandomErrors:
# cuNumeric zipf hangs on the invalid args when LEGATE_TEST=1
@pytest.mark.skipif(LEGATE_TEST, reason="Test hang when LEGATE_TEST=1")
@pytest.mark.parametrize(
"dist, expected_exc",
(
(0.77, ValueError),
(-5, ValueError),
(None, TypeError),
((1, 5, 3), ValueError),
),
ids=str,
)
def test_zipf_invalid_dist(self, dist, expected_exc):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
with pytest.raises(expected_exc):
gen_np.zipf(dist)
with pytest.raises(expected_exc):
gen_num.zipf(dist)

@pytest.mark.skipif(LEGATE_TEST, reason="Test hang when LEGATE_TEST=1")
@pytest.mark.parametrize(
"ngood, nbad, nsample",
((200, 60, 500), ((1, 5, 7), 6, 22)),
ids=str,
)
def test_hypergeometric_invalid_args(self, ngood, nbad, nsample):
expected_exc = ValueError
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
with pytest.raises(expected_exc):
gen_np.hypergeometric(ngood, nbad, nsample)
with pytest.raises(expected_exc):
gen_num.hypergeometric(ngood, nbad, nsample)


if __name__ == "__main__":
import sys

Expand Down
32 changes: 32 additions & 0 deletions tests/integration/test_random_beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,38 @@ def test_noncentral_f_float64(t):
assert_distribution(a, theo_mean, theo_std)


FUNC_ARGS = (
("beta", (2.0, 5.0)),
("f", (1.0, 48.0)),
("logseries", (0.66,)),
("noncentral_f", (1.0, 48.0, 1.414)),
)


@pytest.mark.parametrize("func, args", FUNC_ARGS, ids=str)
@pytest.mark.parametrize("size", ((2048 * 2048), (4096,), 25535), ids=str)
def test_beta_sizes(func, args, size):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
a_np = getattr(gen_np, func)(*args, size=size)
a_num = getattr(gen_num, func)(*args, size=size)
assert a_np.shape == a_num.shape


@pytest.mark.xfail
@pytest.mark.parametrize("func, args", FUNC_ARGS, ids=str)
def test_beta_size_none(func, args):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
a_np = getattr(gen_np, func)(*args, size=None)
a_num = getattr(gen_num, func)(*args, size=None)
# cuNumeric returns singleton array
# NumPy returns scalar
assert np.ndim(a_np) == np.ndim(a_num)


if __name__ == "__main__":
import sys

Expand Down
125 changes: 125 additions & 0 deletions tests/integration/test_random_bitgenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ def test_bitgenerator_type(t):
print(f"DONE for type = {t}")


@pytest.mark.parametrize("size", ((2048 * 2048), (4096,), 25535), ids=str)
def test_bitgenerator_size(size):
seed = 42
gen_np = np.random.PCG64(seed=seed)
gen_num = num.random.XORWOW(seed=seed)
a_np = gen_np.random_raw(size=size)
a_num = gen_num.random_raw(shape=size)
assert a_np.shape == a_num.shape


@pytest.mark.xfail
def test_bitgenerator_size_none():
seed = 42
gen_np = np.random.PCG64(seed=seed)
gen_num = num.random.XORWOW(seed=seed)
a_np = gen_np.random_raw(size=None)
a_num = gen_num.random_raw(shape=None)
# cuNumeric returns singleton array
# NumPy returns scalar
assert np.ndim(a_np) == np.ndim(a_num)


@pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str)
def test_force_build(t):
t(42, True)
Expand Down Expand Up @@ -83,6 +105,15 @@ def test_integers_int16(t):
print(f"1024*1024 sum = {a.sum()}")


@pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str)
def test_integers_endpoint(t):
high = 10
bitgen = t(seed=42)
gen = num.random.Generator(bitgen)
a = gen.integers(high, size=100, dtype=np.int16, endpoint=True)
assert np.max(a) == high


@pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str)
def test_random_float32(t):
# top-level random function has a different signature
Expand All @@ -105,6 +136,20 @@ def test_random_float64(t):
assert_distribution(a, 0.5, num.sqrt(1.0 / 12.0))


def test_random_out_stats():
seed = 42
dtype = np.float32
out_shape = (2, 3, 1)
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
out_np = np.empty(out_shape, dtype=dtype)
out_num = num.empty(out_shape, dtype=dtype)
gen_np.random(out=out_np, dtype=dtype)
gen_num.random(out=out_num, dtype=dtype)
assert out_np.shape == out_num.shape
assert out_np.dtype == out_np.dtype


@pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str)
def test_lognormal_float32(t):
bitgen = t(seed=42)
Expand Down Expand Up @@ -192,6 +237,86 @@ def test_poisson(t):
assert_distribution(a, theo_mean, theo_std)


FUNCS = ("poisson", "normal", "lognormal", "uniform")


@pytest.mark.parametrize("func", FUNCS, ids=str)
@pytest.mark.parametrize("size", ((2048 * 2048), (4096,), 25535), ids=str)
def test_random_sizes(func, size):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
a_np = getattr(gen_np, func)(size=size)
a_num = getattr(gen_num, func)(size=size)
assert a_np.shape == a_num.shape


@pytest.mark.xfail
@pytest.mark.parametrize("func", FUNCS, ids=str)
def test_random_size_none(func):
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
a_np = getattr(gen_np, func)(size=None)
a_num = getattr(gen_num, func)(size=None)
# cuNumeric returns singleton array
# NumPy returns scalar
assert np.ndim(a_np) == np.ndim(a_num)


class TestBitGeneratorErrors:
def test_init_bitgenerator(self):
expected_exc = NotImplementedError
with pytest.raises(expected_exc):
np.random.BitGenerator()
with pytest.raises(expected_exc):
num.random.BitGenerator()

@pytest.mark.xfail
@pytest.mark.parametrize("dtype", (np.int32, np.float128, str))
def test_random_invalid_dtype(self, dtype):
expected_exc = TypeError
seed = 42
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
with pytest.raises(expected_exc):
gen_np.random(size=(1024 * 1024,), dtype=dtype)
# TypeError: Unsupported dtype dtype('int32') for random
with pytest.raises(expected_exc):
gen_num.random(size=(1024 * 1024,), dtype=dtype)
# NotImplementedError: type for random.uniform has to be float64
# or float32

def test_random_out_dtype_mismatch(self):
expected_exc = TypeError
seed = 42
dtype = np.float32
out_shape = (3, 2, 3)
out_dtype = np.float64
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
out_np = np.empty(out_shape, dtype=out_dtype)
out_num = num.empty(out_shape, dtype=out_dtype)
with pytest.raises(expected_exc):
gen_np.random(out=out_np, dtype=dtype)
with pytest.raises(expected_exc):
gen_num.random(out=out_num, dtype=dtype)

def test_random_out_shape_mismatch(self):
expected_exc = ValueError
seed = 42
size = (1024 * 1024,)
out_shape = (3, 2, 3)
gen_np = np.random.Generator(np.random.PCG64(seed=seed))
gen_num = num.random.Generator(num.random.XORWOW(seed=seed))
out_np = np.empty(out_shape)
out_num = num.empty(out_shape)
with pytest.raises(expected_exc):
gen_np.random(size=size, out=out_np)
with pytest.raises(expected_exc):
gen_num.random(size=size, out=out_num)


if __name__ == "__main__":
import sys

Expand Down
Loading

0 comments on commit 46862c4

Please sign in to comment.