Skip to content

Commit 598ea2d

Browse files
authored
Merge pull request #176 from IntelPython/update_randint_untyped
Update `randint_untyped` to reuse `randint` implementation
2 parents 67c705c + a754d92 commit 598ea2d

5 files changed

Lines changed: 60 additions & 194 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
* Sped up `randint` for `bool`, `uint8`, `int8`, `uint16` and `int16`; generated values are unchanged [gh-172](https://github.com/IntelPython/mkl_random/pull/172)
1919
* Raised the minimum build-time `Cython` requirement to `3.1.0`, the first release providing the `freethreading_compatible` directive [gh-159](https://github.com/IntelPython/mkl_random/pull/159)
2020
* Extended the `memcpy`-based fast path of `shuffle` to multi-dimensional `ndarray` inputs whose first-axis items are contiguous, which is also much faster than the previous buffered path [gh-159](https://github.com/IntelPython/mkl_random/pull/159)
21+
* Updated the `randint_untyped` implementation, which now supports `array_like` (broadcastable) `low`/`high` bounds [gh-176](https://github.com/IntelPython/mkl_random/pull/176)
2122

2223
### Fixed
2324
* Fixed `uniform` to return a Python `float` for scalar bounds with `size=None` instead of a 0-d array [gh-167](https://github.com/IntelPython/mkl_random/pull/167)
@@ -28,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2829
* Fixed a memory leak in `logseries`, which leaked a temporary buffer on every call [gh-159](https://github.com/IntelPython/mkl_random/pull/159)
2930
* Fixed `randint` with scalar bounds above `INT_MAX`, `tomaxint` and `bytes` returning uninitialized data for `WH`, `MCG31`, `R250` and `MRG32K3A`, which lack `viRngUniformBits` support [gh-175](https://github.com/IntelPython/mkl_random/pull/175)
3031
* Fixed 64-bit integer generation returning zeros or crashing with `PHILOX4X32X10` and `ARS5` for requests of `2**30` elements or more [gh-175](https://github.com/IntelPython/mkl_random/pull/175)
32+
* Fixed `randint_untyped` raising `OverflowError` for bounds outside the C `long` range, e.g. `2**40` on Windows [gh-176](https://github.com/IntelPython/mkl_random/pull/176)
3133

3234
### Removed
3335
* Removed the `python-gil` constraint from the conda recipes, which pinned `mkl_random` to GIL-enabled Python 3.14 builds [gh-159](https://github.com/IntelPython/mkl_random/pull/159)

‎mkl_random/mklrand.pyx‎

Lines changed: 28 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,6 @@ cdef extern from "mkl_distributions.h":
323323
) noexcept nogil
324324

325325
# random integers madness
326-
void irk_discrete_uniform_vec(
327-
irk_state *state, cnp.npy_intp len, int *res, int low, int high
328-
) noexcept nogil
329-
void irk_discrete_uniform_long_vec(
330-
irk_state *state, cnp.npy_intp len, long *res, long low, long high
331-
) noexcept nogil
332326
void irk_rand_bool_vec(
333327
irk_state *state,
334328
cnp.npy_intp len,
@@ -7066,23 +7060,24 @@ cdef class MKLRandomState(_MKLRandomState):
70667060

70677061
def randint_untyped(self, low, high=None, size=None):
70687062
"""
7069-
randint_untyped(low, high=None, size=None, dtype=int)
7063+
randint_untyped(low, high=None, size=None)
70707064
70717065
Return random integers from `low` (inclusive) to `high` (exclusive).
70727066
7073-
Return random integers from the "discrete uniform" distribution of
7074-
the specified dtype in the "half-open" interval [`low`, `high`). If
7075-
`high` is None (the default), then results are from [0, `low`).
7067+
Same as `randint`, except that the result dtype is not selectable:
7068+
`int32` is used when both bounds fit it, `int64` otherwise.
70767069
70777070
Parameters
70787071
----------
7079-
low : int
7072+
low : int or array_like of ints
70807073
Lowest (signed) integer to be drawn from the distribution (unless
70817074
``high=None``, in which case this parameter is the *highest* such
7082-
integer).
7083-
high : int, optional
7075+
integer). If an array is given, it must broadcast with `high` (and
7076+
with `size`, if provided).
7077+
high : int or array_like of ints, optional
70847078
If provided, one above the largest (signed) integer to be drawn
70857079
from the distribution (see above for behavior if ``high=None``).
7080+
If an array is given, it must broadcast with `low`.
70867081
size : int or tuple of ints, optional
70877082
Output shape. If the given shape is, e.g., ``(m, n, k)``, then
70887083
``m * n * k`` samples are drawn. Default is None, in which case a
@@ -7096,79 +7091,35 @@ cdef class MKLRandomState(_MKLRandomState):
70967091
70977092
See Also
70987093
--------
7099-
random.random_integers : similar to `randint`, only for the closed
7100-
interval [`low`, `high`], and 1 is the lowest value if `high` is
7101-
omitted. In particular, this other one is the one to use to generate
7102-
uniformly distributed discrete non-integers.
7094+
randint : same distribution, with a selectable result dtype.
71037095
71047096
Examples
71057097
--------
7106-
>>> mkl_random.randint(2, size=10)
7107-
array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0])
7108-
>>> mkl_random.randint(1, size=10)
7109-
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
7110-
7111-
Generate a 2 x 4 array of ints between 0 and 4, inclusive:
7112-
7113-
>>> mkl_random.randint(5, size=(2, 4))
7114-
array([[4, 0, 2, 1],
7115-
[3, 2, 2, 0]])
7098+
>>> mkl_random.RandomState().randint_untyped(5, size=(2, 4))
7099+
array([[4, 0, 2, 1], # random
7100+
[3, 2, 2, 0]], dtype=int32)
71167101
71177102
"""
7118-
cdef long lo, hi
7119-
cdef long *array_long_data
7120-
cdef int * array_int_data
7121-
cdef cnp.ndarray array "arrayObject"
7122-
cdef cnp.npy_intp length
7123-
cdef int rv_int
7124-
cdef long rv_long
7125-
71267103
if high is None:
7127-
lo = 0
7128-
hi = low
7104+
high = low
7105+
low = 0
7106+
7107+
# untyped: narrowest of int32/int64 holding both bounds,
7108+
# `initial` guards empty ones
7109+
lo_min = low if np.isscalar(low) else np.min(low, initial=0)
7110+
hi_max = high if np.isscalar(high) else np.max(high, initial=0)
7111+
if (-2**31 <= lo_min) and (hi_max <= 2**31 - 1):
7112+
_dtype = np.int32
71297113
else:
7130-
lo = low
7131-
hi = high
7114+
_dtype = np.int64
71327115

7133-
if lo >= hi :
7134-
raise ValueError("low >= high")
7116+
res = self.randint(low, high, size=size, dtype=_dtype)
71357117

7136-
if ((<int> lo) == lo) and ((<int>hi) == hi):
7137-
if size is None:
7138-
with self.lock, nogil:
7139-
irk_discrete_uniform_vec(
7140-
self.internal_state, 1, &rv_int, <int>lo, <int>hi
7141-
)
7142-
return rv_int
7143-
else:
7144-
array = <cnp.ndarray>np.empty(size, np.int32)
7145-
length = cnp.PyArray_SIZE(array)
7146-
array_int_data = <int*>cnp.PyArray_DATA(array)
7147-
with self.lock, nogil:
7148-
irk_discrete_uniform_vec(
7149-
self.internal_state,
7150-
length,
7151-
array_int_data,
7152-
<int>lo,
7153-
<int>hi
7154-
)
7155-
return array
7156-
else:
7157-
if size is None:
7158-
with self.lock, nogil:
7159-
irk_discrete_uniform_long_vec(
7160-
self.internal_state, 1, &rv_long, lo, hi
7161-
)
7162-
return rv_long
7163-
else:
7164-
array = <cnp.ndarray>np.empty(size, int)
7165-
length = cnp.PyArray_SIZE(array)
7166-
array_long_data = <long*>cnp.PyArray_DATA(array)
7167-
with self.lock, nogil:
7168-
irk_discrete_uniform_long_vec(
7169-
self.internal_state, length, array_long_data, lo, hi
7170-
)
7171-
return array
7118+
# a single sample has always been a Python int
7119+
if size is None and res.ndim == 0:
7120+
return int(res)
7121+
7122+
return res
71727123

71737124
def multinormal_cholesky(self, mean, ch, size=None, method=ICDF):
71747125
"""

‎mkl_random/src/mkl_distributions.cpp‎

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,32 +1594,6 @@ void irk_logseries_vec(irk_state *state,
15941594
mkl_free(Uvec);
15951595
}
15961596

1597-
/* samples discrete uniforms from [low, high) */
1598-
void irk_discrete_uniform_vec(irk_state *state,
1599-
npy_intp len,
1600-
int *res,
1601-
const int low,
1602-
const int high)
1603-
{
1604-
int err = 0;
1605-
1606-
if (len < 1)
1607-
return;
1608-
1609-
while (len > MKL_INT_MAX) {
1610-
err = viRngUniform(VSL_RNG_METHOD_UNIFORM_STD, state->stream,
1611-
MKL_INT_MAX, res, low, high);
1612-
assert(err == VSL_STATUS_OK);
1613-
1614-
res += MKL_INT_MAX;
1615-
len -= MKL_INT_MAX;
1616-
}
1617-
1618-
err = viRngUniform(VSL_RNG_METHOD_UNIFORM_STD, state->stream, len, res, low,
1619-
high);
1620-
assert(err == VSL_STATUS_OK);
1621-
}
1622-
16231597
/*
16241598
* Bulk source of raw uniform words for the bounded-integer
16251599
* routines below, overloaded on the word type (32/64-bit). BRNGs that lack
@@ -1717,85 +1691,6 @@ void irk_uniform_bits32_vec(irk_state *state, npy_intp len, npy_uint32 *res)
17171691
irk_uniform_bits_vec(state, len, res);
17181692
}
17191693

1720-
void irk_discrete_uniform_long_vec(irk_state *state,
1721-
npy_intp len,
1722-
long *res,
1723-
const long low,
1724-
const long high)
1725-
{
1726-
int err = 0;
1727-
unsigned long max;
1728-
npy_intp i = 0;
1729-
1730-
if (len < 1)
1731-
return;
1732-
1733-
while (len > MKL_INT_MAX) {
1734-
irk_discrete_uniform_long_vec(state, MKL_INT_MAX, res, low, high);
1735-
1736-
res += MKL_INT_MAX;
1737-
len -= MKL_INT_MAX;
1738-
}
1739-
1740-
max = ((unsigned long)high) - ((unsigned long)low) - 1UL;
1741-
if (max == 0) {
1742-
DIST_PRAGMA_VECTOR
1743-
for (i = 0; i < len; ++i)
1744-
res[i] = low;
1745-
1746-
return;
1747-
}
1748-
1749-
if (max <= (unsigned long)INT_MAX) {
1750-
int *buf = (int *)mkl_malloc(len * sizeof(int), 64);
1751-
assert(buf != nullptr);
1752-
1753-
err = viRngUniform(VSL_RNG_METHOD_UNIFORM_STD, state->stream, len, buf,
1754-
-1, (int)max);
1755-
assert(err == VSL_STATUS_OK);
1756-
1757-
DIST_PRAGMA_VECTOR
1758-
for (i = 0; i < len; ++i)
1759-
res[i] = low + ((long)buf[i]) + 1L;
1760-
1761-
mkl_free(buf);
1762-
}
1763-
else {
1764-
unsigned long mask = max;
1765-
unsigned long *buf = nullptr;
1766-
int n_accepted;
1767-
1768-
/* Smallest bit mask >= max */
1769-
mask |= mask >> 1;
1770-
mask |= mask >> 2;
1771-
mask |= mask >> 4;
1772-
mask |= mask >> 8;
1773-
mask |= mask >> 16;
1774-
#if ULONG_MAX > 0xffffffffUL
1775-
mask |= mask >> 32;
1776-
#endif
1777-
1778-
buf = (unsigned long *)mkl_malloc(len * sizeof(long), 64);
1779-
assert(buf != nullptr);
1780-
n_accepted = 0;
1781-
1782-
while (n_accepted < len) {
1783-
int k, batchSize = len - n_accepted;
1784-
1785-
irk_uniform_bits_vec(state, batchSize, (npy_uint64 *)buf);
1786-
1787-
for (k = 0; k < batchSize; ++k) {
1788-
unsigned long value = buf[k] & mask;
1789-
if (value <= max) {
1790-
res[n_accepted++] = low + value;
1791-
}
1792-
}
1793-
}
1794-
1795-
mkl_free(buf);
1796-
}
1797-
}
1798-
17991694
void irk_ulong_vec(irk_state *state, npy_intp len, unsigned long *res)
18001695
{
18011696
if (len < 1)

‎mkl_random/src/mkl_distributions.h‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,18 +249,6 @@ extern "C"
249249
extern void
250250
irk_uniform_bits32_vec(irk_state *state, npy_intp len, npy_uint32 *res);
251251

252-
extern void irk_discrete_uniform_vec(irk_state *state,
253-
npy_intp len,
254-
int *res,
255-
const int low,
256-
const int high);
257-
258-
extern void irk_discrete_uniform_long_vec(irk_state *state,
259-
npy_intp len,
260-
long *res,
261-
const long low,
262-
const long high);
263-
264252
extern void irk_rand_int64_vec(irk_state *state,
265253
npy_intp len,
266254
npy_int64 *res,

‎mkl_random/tests/test_random.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,36 @@ def test_array_bounds_errors(self):
498498
assert_raises(ValueError, rnd.randint, [0, 0], [5, 6], (4,))
499499
assert_raises(ValueError, rnd.randint, [3, 4], [9, 10], ())
500500

501+
def test_untyped_result_type(self):
502+
rs = rnd.MKLRandomState(0)
503+
assert rs.randint_untyped(0, 100, size=10).dtype == np.int32
504+
assert rs.randint_untyped(0, 2**31, size=10).dtype == np.int64
505+
assert rs.randint_untyped(-(2**31) - 1, 0, size=10).dtype == np.int64
506+
assert type(rs.randint_untyped(5)) is int
507+
assert rs.randint_untyped(5, size=()).shape == ()
508+
509+
def test_untyped_array_bounds(self):
510+
low, high = [0, 10, 20], [10, 20, 30]
511+
vals = rnd.MKLRandomState(0).randint_untyped(low, high)
512+
assert vals.dtype == np.int32
513+
assert np.all(vals >= low)
514+
assert np.all(vals < high)
515+
516+
def test_untyped_errors(self):
517+
rs = rnd.MKLRandomState(0)
518+
assert_raises(ValueError, rs.randint_untyped, 5, 5)
519+
assert_raises(ValueError, rs.randint_untyped, [0, 5], [5, 5])
520+
521+
@pytest.mark.parametrize("brng", _ALL_BRNGS)
522+
def test_untyped_range_above_int_max(self, brng):
523+
# a range wider than INT_MAX used to take a separate C long path
524+
hi = 2**40
525+
y = rnd.MKLRandomState(0, brng=brng).randint_untyped(0, hi, size=100000)
526+
assert y.dtype == np.int64
527+
assert y.min() >= 0
528+
assert int(y.max()) < hi
529+
assert len(np.unique(y)) > 99000
530+
501531

502532
class RandomDistData(NamedTuple):
503533
seed: int

0 commit comments

Comments
 (0)