Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions arkouda/numpy/random/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,20 +322,24 @@ def integers(self, low, high=None, size=None, dtype=akint64, endpoint=False):
# those generators must be destroyed individually before the python-side Generator
# is destroyed. This is not strictly necessary, but should prevent memory creep in
# the event of reseeding the global Generator repeatedly.

def destructor(self):
from arkouda.core.client import generic_msg

# If the server restarted, our client-side generator names can be stale.
# Make deletion idempotent: missing symbols should not be fatal.
for chapel_dt in get_registration_config()["parameter_classes"]["array"]["dtype"]:
if chapel_dt not in _supported_chapel_types:
continue
dt = dtype_for_chapel(chapel_dt)
generic_msg(
cmd="delGenerator",
args={
"name": self._name_dict[dt],
},
)
try:
generic_msg(cmd="delGenerator", args={"name": self._name_dict[dt]})
except Exception as e:
msg = str(e).lower()
# Be narrow: only swallow the "unknown symbol" / symbol-not-found failure mode
if ("unknownsymbol" in msg) or ("unknown symbol" in msg):
continue
raise

self.__del__()
return

Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ testpaths =
tests/numpy/alignment_verification/operators_alignment.py
tests/numpy/alignment_verification/strings_alignment.py
tests/numpy/alignment_verification/utils_alignment.py
tests/numpy/alignment_verification/random_legacy_alignment.py
tests/numpy/alignment_verification/sorting_alignment.py
tests/numpy/alignment_verification/random_generator_alignment_test.py
tests/numpy/numeric_test.py
Expand Down
147 changes: 147 additions & 0 deletions tests/numpy/alignment_verification/random_legacy_alignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import numpy as np
import pytest

import arkouda as ak


def _to_np(x):
if hasattr(x, "to_ndarray"):
return x.to_ndarray()
return ak.to_numpy(x)


def _assert_in_range(arr, lo, hi, *, inclusive_hi=False):
a = np.asarray(arr)
assert np.all(a >= lo)
if inclusive_hi:
assert np.all(a <= hi)
else:
assert np.all(a < hi)


def _assert_shape(arr, shape):
a = np.asarray(arr)
assert a.shape == tuple(shape)


def _numpy_randomstate_randint(low, high, size, dtype):
rs = np.random.RandomState(0)
return rs.randint(low=low, high=high, size=size, dtype=dtype)


@pytest.mark.skip_if_rank_not_compiled([2, 3])
@pytest.mark.parametrize("shape", [(1,), (10,), (2, 3), (4, 1, 5)])
def test_ak_rand_shape_and_range(shape):
# Arkouda: rand is under ak.random
ak.random.seed(12345)
out = ak.random.rand(*shape)
out_np = _to_np(out)
_assert_shape(out_np, shape)
# Accept [0,1] to tolerate rare inclusive-high implementations
_assert_in_range(out_np, 0.0, 1.0, inclusive_hi=True)


def test_ak_rand_scalar():
ak.random.seed(12345)
x = ak.random.rand()
assert isinstance(x, (float, np.floating))
assert 0.0 <= float(x) <= 1.0


@pytest.mark.skip_if_rank_not_compiled([2])
@pytest.mark.parametrize("size", [0, 1, 10, (2, 3)])
def test_randint_int64_shape_dtype_and_bounds(size):
low, high = 3, 17
ak.random.seed(2468)

# Arkouda API name: usually randint() under ak.random
out = ak.random.randint(low, high, size=size)

out_np = _to_np(out)

if isinstance(size, tuple):
_assert_shape(out_np, size)
else:
_assert_shape(out_np, (size,))

assert np.issubdtype(out_np.dtype, np.integer)
_assert_in_range(out_np, low, high, inclusive_hi=False)


def _numpy_bool_randint_error(low, high):
rs = np.random.RandomState(0)
with pytest.raises(ValueError) as e:
rs.randint(low=low, high=high, size=10, dtype=bool)
return str(e.value)


@pytest.mark.parametrize(
"low,high",
[
(0, 0),
(0, -1),
(-1, 2),
(0, 3),
(1, 1),
(1, 0),
],
)
def test_randint_bool_validation_messages_match_numpy(low, high):
expected_msg = _numpy_bool_randint_error(low, high)

ak.random.seed(0)
with pytest.raises(ValueError) as e_ak:
ak.random.randint(low, high, size=10, dtype="bool")

actual = str(e_ak.value)
if actual != expected_msg:
pytest.xfail(
f"Arkouda randint(dtype=bool) error msg mismatch for low={low}, high={high}: "
f"expected={expected_msg!r}, got={actual!r}"
f" Issue #5295."
)

assert actual == expected_msg


@pytest.mark.skip_if_rank_not_compiled([2])
@pytest.mark.parametrize("size", [0, 1, 10, (2, 3)])
def test_uniform_shape_and_range(size):
ak.random.seed(1357)

# Many Arkouda builds use ak.random.uniform(low, high, size)
out = ak.random.uniform(low=2.5, high=7.5, size=size)

out_np = _to_np(out)

if isinstance(size, tuple):
_assert_shape(out_np, size)
else:
_assert_shape(out_np, (size,))

_assert_in_range(out_np, 2.5, 7.5, inclusive_hi=True)


def test_standard_normal_basic_moments_are_reasonable():
n = 20000
ak.random.seed(4242)

# Many Arkouda builds use ak.random.standard_normal(size)
out = ak.random.standard_normal(n)

x = _to_np(out).astype(np.float64)
assert abs(float(x.mean())) < 0.05
assert abs(float(x.var()) - 1.0) < 0.08


def test_random_api_scalar_and_vector_range():
ak.random.seed(123)
x = ak.random.random()
assert isinstance(x, (float, np.floating))
assert 0.0 <= float(x) < 1.0

ak.random.seed(123)
y = ak.random.random(1000)
y_np = _to_np(y)
_assert_shape(y_np, (1000,))
_assert_in_range(y_np, 0.0, 1.0, inclusive_hi=False)