Skip to content

Commit 039780a

Browse files
committed
Add more tests for RandomState and RandomState.uniform
1 parent 97c7737 commit 039780a

File tree

3 files changed

+179
-28
lines changed

3 files changed

+179
-28
lines changed

dpnp/random/dpnp_algo_random.pyx

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ and the rest of the library
3434

3535

3636
import numpy
37-
import dpnp.config as config
3837
import dpctl
38+
import numbers
39+
40+
import dpnp.config as config
41+
from dpnp.dpnp_array import dpnp_array
42+
3943
from libc.stdlib cimport free, malloc
4044
from libc.stdint cimport uint32_t
4145

@@ -312,27 +316,39 @@ cdef class MT19937:
312316
raise ValueError("SyclQueue copy failed")
313317

314318
# get a scalar seed value or a vector of seeds
315-
if isinstance(seed, int) and seed >= 0:
316-
scalar_seed = <uint32_t> seed
317-
elif isinstance(seed, (list, tuple)):
318-
is_vector_seed = True
319-
vector_seed_len = len(seed)
320-
if vector_seed_len > 3:
321-
raise ValueError(
322-
f"{vector_seed_len} length of seed vector isn't supported, "
323-
"the length is limited by 3")
324-
325-
vector_seed = <uint32_t *> malloc(vector_seed_len * sizeof(uint32_t))
326-
if (not vector_seed):
327-
raise MemoryError(f"Could not allocate memory for seed vector of length {vector_seed_len}")
328-
329-
# convert input seed's type to uint32_t one (expected in MKL function)
330-
try:
331-
for i in range(vector_seed_len):
332-
vector_seed[i] = <uint32_t> seed[i]
333-
except (ValueError, TypeError) as e:
334-
free(vector_seed)
335-
raise e
319+
if self.is_integer(seed):
320+
if self.is_uint_range(seed):
321+
scalar_seed = <uint32_t> seed
322+
else:
323+
raise ValueError("Seed must be between 0 and 2**32 - 1")
324+
elif isinstance(seed, (list, tuple, range, numpy.ndarray, dpnp_array)):
325+
if len(seed) == 0:
326+
raise ValueError("Seed must be non-empty")
327+
elif numpy.ndim(seed) > 1:
328+
raise ValueError("Seed array must be 1-d")
329+
elif not all([self.is_integer(item) for item in seed]):
330+
raise TypeError("Seed must be a sequence of unsigned int elements")
331+
elif not all([self.is_uint_range(item) for item in seed]):
332+
raise ValueError("Seed must be between 0 and 2**32 - 1")
333+
else:
334+
is_vector_seed = True
335+
vector_seed_len = len(seed)
336+
if vector_seed_len > 3:
337+
raise ValueError(
338+
f"{vector_seed_len} length of seed vector isn't supported, "
339+
"the length is limited by 3")
340+
341+
vector_seed = <uint32_t *> malloc(vector_seed_len * sizeof(uint32_t))
342+
if (not vector_seed):
343+
raise MemoryError(f"Could not allocate memory for seed vector of length {vector_seed_len}")
344+
345+
# convert input seed's type to uint32_t one (expected in MKL function)
346+
try:
347+
for i in range(vector_seed_len):
348+
vector_seed[i] = <uint32_t> seed[i]
349+
except (ValueError, TypeError) as e:
350+
free(vector_seed)
351+
raise e
336352
else:
337353
raise TypeError("Seed must be an unsigned int, or a sequence of unsigned int elements")
338354

@@ -346,6 +362,15 @@ cdef class MT19937:
346362
MT19937_Delete(&self.mt19937)
347363
c_dpctl.DPCTLQueue_Delete(self.q_ref)
348364

365+
cdef bint is_integer(self, value):
366+
if isinstance(value, numbers.Number):
367+
return isinstance(value, int) or isinstance(value, (numpy.int32, numpy.uint32))
368+
# cover an element of dpnp array:
369+
return numpy.ndim(value) == 0 and hasattr(value, "dtype") and numpy.issubdtype(value, (numpy.int32, numpy.uint32))
370+
371+
cdef bint is_uint_range(self, value):
372+
return value >= 0 and value <= numpy.iinfo(numpy.uint32).max
373+
349374
cdef mt19937_struct * get_mt19937(self):
350375
return &self.mt19937
351376

dpnp/random/dpnp_iface_random.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def __init__(self, seed=1, sycl_queue=None):
110110
seed = 1
111111

112112
self.random_state = MT19937(seed, sycl_queue)
113+
self.fallback_random_state = call_origin(numpy.random.RandomState, seed)
113114

114115
def uniform(self, low=0.0, high=1.0, size=None, dtype=numpy.float64, usm_type="device"):
115116
"""
@@ -119,6 +120,13 @@ def uniform(self, low=0.0, high=1.0, size=None, dtype=numpy.float64, usm_type="d
119120
In other words, any value within the given interval is equally likely to be drawn by uniform.
120121
121122
For full documentation refer to :obj:`numpy.random.RandomState.uniform`.
123+
124+
Limitations
125+
-----------
126+
Parameters ``low`` and ``high`` are supported as scalar.
127+
Otherwise, :obj:`numpy.random.uniform(low, high, size)` samples are drawn.
128+
Parameter ``dtype`` is supported only for :obj:`dpnp.float32` or :obj:`dpnp.float64`.
129+
Output array data type is the same as ``dtype``.
122130
"""
123131

124132
if not use_origin_backend(low):
@@ -129,9 +137,11 @@ def uniform(self, low=0.0, high=1.0, size=None, dtype=numpy.float64, usm_type="d
129137
else:
130138
if low > high:
131139
low, high = high, low
140+
if not (dpnp.is_type_supported(dtype) and dtype in {dpnp.float32, dpnp.float64}):
141+
raise TypeError(f"{dtype} is unsupported.")
132142
return self.random_state.uniform(low, high, size, dtype, usm_type).get_pyobj()
133143

134-
return call_origin(numpy.random.uniform, low, high, size)
144+
return call_origin(self.fallback_random_state.uniform, low, high, size)
135145

136146
def _check_dims(dims):
137147
for dim in dims:
@@ -1370,13 +1380,14 @@ def seed(seed=None):
13701380
pass
13711381
else:
13721382
# TODO:
1373-
# migrate to a single approach with RandomState()
1383+
# migrate to a single approach with RandomState class
13741384

1375-
# update a mt19937 random number for both RandomState() class and legacy functionality
1385+
# update a mt19937 random number for both RandomState and legacy functionality
13761386
global dpnp_random_state
13771387
dpnp_random_state = RandomState(seed)
1378-
return dpnp_rng_srand(seed)
1388+
dpnp_rng_srand(seed)
13791389

1390+
# always reseed numpy engine also
13801391
return call_origin(numpy.random.seed, seed)
13811392

13821393

@@ -1596,8 +1607,7 @@ def uniform(low=0.0, high=1.0, size=None, usm_type='device'):
15961607
:obj:`dpnp.random.random` : Floats uniformly distributed over ``[0, 1)``.
15971608
15981609
"""
1599-
dtype = numpy.float64
1600-
return dpnp_random_state.uniform(low, high, size, dtype, usm_type)
1610+
return dpnp_random_state.uniform(low, high, size, dtype=numpy.float64, usm_type=usm_type)
16011611

16021612

16031613
def vonmises(mu, kappa, size=None):

tests/test_randomstate.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import pytest
2+
import unittest
3+
4+
import dpnp
5+
import numpy
6+
7+
from dpnp.random import RandomState
8+
from numpy.testing import (assert_allclose, assert_raises, assert_array_almost_equal)
9+
10+
11+
class TestSeed:
12+
def test_scalar(self):
13+
seed = 28041997
14+
size = (3, 2, 4)
15+
rs = RandomState(seed)
16+
a1 = dpnp.asnumpy(rs.uniform(size=size))
17+
rs = RandomState(seed)
18+
a2 = dpnp.asnumpy(rs.uniform(size=size))
19+
assert_allclose(a1, a2, rtol=1e-07, atol=0)
20+
21+
@pytest.mark.parametrize("seed",
22+
[range(3),
23+
numpy.arange(3, dtype=numpy.int32),
24+
dpnp.arange(3, dtype=numpy.int32),
25+
[0], [4294967295], [2, 7, 15], (1,), (85, 6, 17)],
26+
ids=['range(2)',
27+
'numpy.arange(2)',
28+
'dpnp.arange(2)',
29+
'[0]', '[4294967295]', '[2, 7, 15]', '(1,)', '(85, 6, 17)'])
30+
def test_array_range(self, seed):
31+
size = 15
32+
a1 = dpnp.asnumpy(RandomState(seed).uniform(size=size))
33+
a2 = dpnp.asnumpy(RandomState(seed).uniform(size=size))
34+
assert_allclose(a1, a2, rtol=1e-07, atol=0)
35+
36+
@pytest.mark.parametrize("seed",
37+
[0.5, -1.5, [-0.3], (1.7, 3),
38+
'text',
39+
numpy.arange(0, 1, 0.5),
40+
dpnp.arange(3),
41+
dpnp.arange(3, dtype=numpy.float32)],
42+
ids=['0.5', '-1.5', '[-0.3]', '(1.7, 3)',
43+
'text',
44+
'numpy.arange(0, 1, 0.5)',
45+
'dpnp.arange(3)',
46+
'dpnp.arange(3, dtype=numpy.float32)'])
47+
def test_invalid_type(self, seed):
48+
# seed must be an unsigned 32-bit integer
49+
assert_raises(TypeError, RandomState, seed)
50+
51+
@pytest.mark.parametrize("seed",
52+
[-1, [-3, 7], (17, 3, -5), [4, 3, 2, 1], (7, 6, 5, 1),
53+
range(-1, -11, -1),
54+
numpy.arange(4, dtype=numpy.int32),
55+
dpnp.arange(-3, 3, dtype=numpy.int32),
56+
numpy.iinfo(numpy.uint32).max + 1,
57+
(1, 7, numpy.iinfo(numpy.uint32).max + 1)],
58+
ids=['-1', '[-3, 7]', '(17, 3, -5)', '[4, 3, 2, 1]', '(7, 6, 5, 1)',
59+
'range(-1, -11, -1)',
60+
'numpy.arange(4, dtype=numpy.int32)',
61+
'dpnp.arange(-3, 3, dtype=numpy.int32)',
62+
'numpy.iinfo(numpy.uint32).max + 1',
63+
'(1, 7, numpy.iinfo(numpy.uint32).max + 1)'])
64+
def test_invalid_value(self, seed):
65+
# seed must be an unsigned 32-bit integer
66+
assert_raises(ValueError, RandomState, seed)
67+
68+
@pytest.mark.parametrize("seed",
69+
[[], (),
70+
[[1, 2, 3]],
71+
[[1, 2, 3], [4, 5, 6]],
72+
numpy.array([], dtype=numpy.int64),
73+
dpnp.array([], dtype=numpy.int64)],
74+
ids=['[]', '()',
75+
'[[1, 2, 3]]',
76+
'[[1, 2, 3], [4, 5, 6]]',
77+
'numpy.array([], dtype=numpy.int64)',
78+
'dpnp.array([], dtype=numpy.int64)'])
79+
def test_invalid_shape(self, seed):
80+
# seed must be an unsigned or 1-D array
81+
assert_raises(ValueError, RandomState, seed)
82+
83+
class TestUniform:
84+
@pytest.mark.parametrize("dtype",
85+
[dpnp.float32, dpnp.float64, numpy.float32, numpy.float64],
86+
ids=['dpnp.float32', 'dpnp.float64', 'numpy.float32', 'numpy.float64'])
87+
@pytest.mark.parametrize("usm_type",
88+
["host", "device", "shared"],
89+
ids=['host', 'device', 'shared'])
90+
def test_uniform(self, dtype, usm_type):
91+
seed = 28041997
92+
actual = dpnp.asnumpy(RandomState(seed).uniform(low=1.23, high=10.54, size=(3, 2), dtype=dtype, usm_type=usm_type))
93+
desired = numpy.array([[3.700744485249743, 8.390019132522866],
94+
[2.60340195777826, 4.473366308724508],
95+
[1.773701806552708, 4.193498786306009]])
96+
assert_array_almost_equal(actual, desired, decimal=6)
97+
98+
@pytest.mark.parametrize("high",
99+
[dpnp.array([3]), numpy.array([3])],
100+
ids=['dpnp.array([3])', 'numpy.array([3])'])
101+
@pytest.mark.parametrize("low",
102+
[[2], dpnp.array([2]), numpy.array([2])],
103+
ids=['[2]', 'dpnp.array([2])', 'numpy.array([2])'])
104+
def test_fallback(self, low, high):
105+
seed = 15
106+
# dpnp accepts only scalar as low and/or high, in other case it will be a fallback to numpy
107+
actual = dpnp.asnumpy(RandomState(seed).uniform(low=low, high=high, size=(3, 2, 5)))
108+
desired = numpy.random.RandomState(seed).uniform(low=low, high=high, size=(3, 2, 5))
109+
assert_array_almost_equal(actual, desired, decimal=15)
110+
111+
@pytest.mark.parametrize("dtype",
112+
[dpnp.float16, numpy.integer, dpnp.int, dpnp.bool, numpy.int64, dpnp.int32],
113+
ids=['dpnp.float16', 'numpy.integer', 'dpnp.int', 'dpnp.bool', 'numpy.int64', 'dpnp.int32'])
114+
def test_invalid_dtype(self, dtype):
115+
# dtype must be float32 or float64
116+
assert_raises(TypeError, RandomState().uniform, dtype=dtype)

0 commit comments

Comments
 (0)