diff options
author | Charles Harris <charlesr.harris@gmail.com> | 2016-01-03 15:29:31 -0700 |
---|---|---|
committer | Charles Harris <charlesr.harris@gmail.com> | 2016-01-03 15:29:31 -0700 |
commit | 9fb9288f461c7c7af9708f136fa4f29acdc734a8 (patch) | |
tree | e4848c8e1ad3298b2c0856e446566388ce87f5d7 | |
parent | 5dab9275475e70630d7b1242c3530a6726c61fa2 (diff) | |
parent | bc2a97bde26a026e75adec7ec70566be3005c47c (diff) | |
download | numpy-9fb9288f461c7c7af9708f136fa4f29acdc734a8.tar.gz |
Merge pull request #6910 from charris/add-64-bit-random-int
ENH: Add dtype argument to random.randint.
-rw-r--r-- | appveyor.yml | 6 | ||||
-rw-r--r-- | doc/release/1.11.0-notes.rst | 17 | ||||
-rw-r--r-- | numpy/core/include/numpy/npy_common.h | 3 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarray_tests.c.src | 2 | ||||
-rw-r--r-- | numpy/core/tests/test_mem_overlap.py | 41 | ||||
-rw-r--r-- | numpy/random/mtrand/mt_compat.h | 68 | ||||
-rw-r--r-- | numpy/random/mtrand/mtrand.pyx | 390 | ||||
-rw-r--r-- | numpy/random/mtrand/numpy.pxd | 16 | ||||
-rw-r--r-- | numpy/random/mtrand/randomkit.c | 224 | ||||
-rw-r--r-- | numpy/random/mtrand/randomkit.h | 41 | ||||
-rw-r--r-- | numpy/random/tests/test_random.py | 77 | ||||
-rwxr-xr-x | tools/travis-test.sh | 6 |
12 files changed, 820 insertions, 71 deletions
diff --git a/appveyor.yml b/appveyor.yml index 59389462d..e9467e093 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,12 +12,6 @@ environment: - PY_MAJOR_VER: 3 PYTHON_ARCH: "x86" -matrix: - #fast_finish: true - allow_failures: - - PY_MAJOR_VER: 2 - - PY_MAJOR_VER: 3 - build_script: - ps: Start-FileDownload "https://repo.continuum.io/miniconda/Miniconda$env:PY_MAJOR_VER-latest-Windows-$env:PYTHON_ARCH.exe" C:\Miniconda.exe; echo "Finished downloading miniconda" - cmd: C:\Miniconda.exe /S /D=C:\Py diff --git a/doc/release/1.11.0-notes.rst b/doc/release/1.11.0-notes.rst index 7c078eed9..705ce73c1 100644 --- a/doc/release/1.11.0-notes.rst +++ b/doc/release/1.11.0-notes.rst @@ -86,6 +86,23 @@ New Features the files can be specifies to be ``*.f90``. The ``verbose`` argument is also activated, it was previously ignored. +* A ``dtype`` parameter has been added to ``np.random.randint`` + Random ndarrays of the following types can now be generated: + + - np.bool, + - np.int8, np.uint8, + - np.int16, np.uint16, + - np.int32, np.uint32, + - np.int64, np.uint64, + - np.int_ (long), np.intp + + The specification is by precision rather than by C type. Hence, on some + platforms np.int64 may be a `long` instead of `long long` even if the + specified dtype is `long long` because the two may have the same + precision. The resulting type depends on which c type numpy uses for the + given precision. The byteorder specification is also ignored, the + generated arrays are always in native byte order. + Improvements ============ diff --git a/numpy/core/include/numpy/npy_common.h b/numpy/core/include/numpy/npy_common.h index 47ef94c92..baf5549d9 100644 --- a/numpy/core/include/numpy/npy_common.h +++ b/numpy/core/include/numpy/npy_common.h @@ -7,6 +7,9 @@ #include <npy_config.h> #endif +/* need Python.h for npy_intp, npy_uintp */ +#include <Python.h> + /* * gcc does not unroll even with -O3 * use with care, unrolling on modern cpus rarely speeds things up diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src index 5e247e15e..45092dc0c 100644 --- a/numpy/core/src/multiarray/multiarray_tests.c.src +++ b/numpy/core/src/multiarray/multiarray_tests.c.src @@ -778,7 +778,7 @@ static PyObject * test_as_c_array(PyObject *NPY_UNUSED(self), PyObject *args) { PyArrayObject *array_obj; - npy_intp dims[3]; // max 3-dim + npy_intp dims[3]; /* max 3-dim */ npy_intp i=0, j=0, k=0; npy_intp num_dims = 0; PyArray_Descr *descr = NULL; diff --git a/numpy/core/tests/test_mem_overlap.py b/numpy/core/tests/test_mem_overlap.py index a8b29ecd1..82e66db5b 100644 --- a/numpy/core/tests/test_mem_overlap.py +++ b/numpy/core/tests/test_mem_overlap.py @@ -110,17 +110,19 @@ def test_diophantine_fuzz(): numbers = [] while min(feasible_count, infeasible_count) < min_count: # Ensure big and small integer problems - A_max = 1 + rng.randint(0, 11)**6 - U_max = rng.randint(0, 11)**6 + A_max = 1 + rng.randint(0, 11, dtype=np.intp)**6 + U_max = rng.randint(0, 11, dtype=np.intp)**6 A_max = min(max_int, A_max) U_max = min(max_int-1, U_max) - A = tuple(rng.randint(1, A_max+1) for j in range(ndim)) - U = tuple(rng.randint(0, U_max+2) for j in range(ndim)) + A = tuple(rng.randint(1, A_max+1, dtype=np.intp) + for j in range(ndim)) + U = tuple(rng.randint(0, U_max+2, dtype=np.intp) + for j in range(ndim)) b_ub = min(max_int-2, sum(a*ub for a, ub in zip(A, U))) - b = rng.randint(-1, b_ub+2) + b = rng.randint(-1, b_ub+2, dtype=np.intp) if ndim == 0 and feasible_count < min_count: b = 0 @@ -258,9 +260,9 @@ def check_may_share_memory_easy_fuzz(get_max_work, same_steps, min_count): rng = np.random.RandomState(1234) def random_slice(n, step): - start = rng.randint(0, n+1) - stop = rng.randint(start, n+1) - if rng.randint(0, 2) == 0: + start = rng.randint(0, n+1, dtype=np.intp) + stop = rng.randint(start, n+1, dtype=np.intp) + if rng.randint(0, 2, dtype=np.intp) == 0: stop, start = start, stop step *= -1 return slice(start, stop, step) @@ -269,12 +271,14 @@ def check_may_share_memory_easy_fuzz(get_max_work, same_steps, min_count): infeasible = 0 while min(feasible, infeasible) < min_count: - steps = tuple(rng.randint(1, 11) if rng.randint(0, 5) == 0 else 1 + steps = tuple(rng.randint(1, 11, dtype=np.intp) + if rng.randint(0, 5, dtype=np.intp) == 0 else 1 for j in range(x.ndim)) if same_steps: steps2 = steps else: - steps2 = tuple(rng.randint(1, 11) if rng.randint(0, 5) == 0 else 1 + steps2 = tuple(rng.randint(1, 11, dtype=np.intp) + if rng.randint(0, 5, dtype=np.intp) == 0 else 1 for j in range(x.ndim)) t1 = np.arange(x.ndim) @@ -374,9 +378,9 @@ def test_internal_overlap_slices(): rng = np.random.RandomState(1234) def random_slice(n, step): - start = rng.randint(0, n+1) - stop = rng.randint(start, n+1) - if rng.randint(0, 2) == 0: + start = rng.randint(0, n+1, dtype=np.intp) + stop = rng.randint(start, n+1, dtype=np.intp) + if rng.randint(0, 2, dtype=np.intp) == 0: stop, start = start, stop step *= -1 return slice(start, stop, step) @@ -385,7 +389,8 @@ def test_internal_overlap_slices(): min_count = 5000 while cases < min_count: - steps = tuple(rng.randint(1, 11) if rng.randint(0, 5) == 0 else 1 + steps = tuple(rng.randint(1, 11, dtype=np.intp) + if rng.randint(0, 5, dtype=np.intp) == 0 else 1 for j in range(x.ndim)) t1 = np.arange(x.ndim) rng.shuffle(t1) @@ -469,10 +474,12 @@ def test_internal_overlap_fuzz(): rng = np.random.RandomState(1234) while min(overlap, no_overlap) < min_count: - ndim = rng.randint(1, 4) + ndim = rng.randint(1, 4, dtype=np.intp) - strides = tuple(rng.randint(-1000, 1000) for j in range(ndim)) - shape = tuple(rng.randint(1, 30) for j in range(ndim)) + strides = tuple(rng.randint(-1000, 1000, dtype=np.intp) + for j in range(ndim)) + shape = tuple(rng.randint(1, 30, dtype=np.intp) + for j in range(ndim)) a = as_strided(x, strides=strides, shape=shape) result = check_internal_overlap(a) diff --git a/numpy/random/mtrand/mt_compat.h b/numpy/random/mtrand/mt_compat.h new file mode 100644 index 000000000..ab56a553c --- /dev/null +++ b/numpy/random/mtrand/mt_compat.h @@ -0,0 +1,68 @@ +/* + * This is a convenience header file providing compatibility utilities + * for supporting Python 2 and Python 3 in the same code base. + * + * It can be removed when Python 2.6 is dropped as PyCapsule is available + * in both Python 3.1+ and Python 2.7. + */ + +#ifndef _MT_COMPAT_H_ +#define _MT_COMPAT_H_ + +#include <Python.h> +#include <numpy/npy_common.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * PyCObject functions adapted to PyCapsules. + * + * The main job here is to get rid of the improved error handling + * of PyCapsules. It's a shame... + */ +#if PY_VERSION_HEX >= 0x03000000 + +static NPY_INLINE PyObject * +NpyCapsule_FromVoidPtr(void *ptr, void (*dtor)(PyObject *)) +{ + PyObject *ret = PyCapsule_New(ptr, NULL, dtor); + if (ret == NULL) { + PyErr_Clear(); + } + return ret; +} + +static NPY_INLINE void * +NpyCapsule_AsVoidPtr(PyObject *obj) +{ + void *ret = PyCapsule_GetPointer(obj, NULL); + if (ret == NULL) { + PyErr_Clear(); + } + return ret; +} + +#else + +static NPY_INLINE PyObject * +NpyCapsule_FromVoidPtr(void *ptr, void (*dtor)(void *)) +{ + return PyCObject_FromVoidPtr(ptr, dtor); +} + +static NPY_INLINE void * +NpyCapsule_AsVoidPtr(PyObject *ptr) +{ + return PyCObject_AsVoidPtr(ptr); +} + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _COMPAT_H_ */ diff --git a/numpy/random/mtrand/mtrand.pyx b/numpy/random/mtrand/mtrand.pyx index 655b708b3..110b60a9b 100644 --- a/numpy/random/mtrand/mtrand.pyx +++ b/numpy/random/mtrand/mtrand.pyx @@ -67,6 +67,17 @@ cdef extern from "randomkit.h": rk_error rk_altfill(void *buffer, size_t size, int strong, rk_state *state) nogil double rk_gauss(rk_state *state) nogil + void rk_random_uint64(npy_uint64 off, npy_uint64 rng, npy_intp cnt, + npy_uint64 *out, rk_state *state) nogil + void rk_random_uint32(npy_uint32 off, npy_uint32 rng, npy_intp cnt, + npy_uint32 *out, rk_state *state) nogil + void rk_random_uint16(npy_uint16 off, npy_uint16 rng, npy_intp cnt, + npy_uint16 *out, rk_state *state) nogil + void rk_random_uint8(npy_uint8 off, npy_uint8 rng, npy_intp cnt, + npy_uint8 *out, rk_state *state) nogil + void rk_random_bool(npy_bool off, npy_bool rng, npy_intp cnt, + npy_bool *out, rk_state *state) nogil + cdef extern from "distributions.h": # do not need the GIL, but they do need a lock on the state !! */ @@ -131,6 +142,7 @@ cimport cython import numpy as np import operator import warnings + try: from threading import Lock except ImportError: @@ -569,6 +581,304 @@ def _shape_from_size(size, d): shape = tuple(size) + (d,) return shape + +# Set up dictionary of integer types and relevant functions. +# +# The dictionary is keyed by dtype(...).name and the values +# are a tuple (low, high, function), where low and high are +# the bounds of the largest half open interval `[low, high)` +# and the function is the relevant function to call for +# that precision. +# +# The functions are all the same except for changed types in +# a few places. It would be easy to template them. + +def _rand_bool(low, high, size, rngstate): + """ + _rand_bool(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_bool off, rng, buf + cdef npy_bool *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_bool>(high - low) + off = <npy_bool>(low) + if size is None: + rk_random_bool(off, rng, 1, &buf, state) + return buf + else: + array = <ndarray>np.empty(size, np.bool_) + cnt = PyArray_SIZE(array) + out = <npy_bool *>PyArray_DATA(array) + with nogil: + rk_random_bool(off, rng, cnt, out, state) + return array + + +def _rand_int8(low, high, size, rngstate): + """ + _rand_int8(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint8 off, rng, buf + cdef npy_uint8 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint8>(high - low) + off = <npy_uint8>(<npy_int8>low) + if size is None: + rk_random_uint8(off, rng, 1, &buf, state) + return <npy_int8>buf + else: + array = <ndarray>np.empty(size, np.int8) + cnt = PyArray_SIZE(array) + out = <npy_uint8 *>PyArray_DATA(array) + with nogil: + rk_random_uint8(off, rng, cnt, out, state) + return array + + +def _rand_int16(low, high, size, rngstate): + """ + _rand_int16(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint16 off, rng, buf + cdef npy_uint16 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint16>(high - low) + off = <npy_uint16>(<npy_int16>low) + if size is None: + rk_random_uint16(off, rng, 1, &buf, state) + return <npy_int16>buf + else: + array = <ndarray>np.empty(size, np.int16) + cnt = PyArray_SIZE(array) + out = <npy_uint16 *>PyArray_DATA(array) + with nogil: + rk_random_uint16(off, rng, cnt, out, state) + return array + + +def _rand_int32(low, high, size, rngstate): + """ + _rand_int32(self, low, high, size, rngstate) + + Return random np.int32 integers between `low` and `high`, inclusive. + + Return random integers from the "discrete uniform" distribution in the + closed interval [`low`, `high`). If `high` is None (the default), + then results are from [0, `low`). On entry the arguments are presumed + to have been validated for size and order for the np.int32 type. + + Parameters + ---------- + low : int + Lowest (signed) integer to be drawn from the distribution (unless + ``high=None``, in which case this parameter is the *highest* such + integer). + high : int + If provided, the largest (signed) integer to be drawn from the + distribution (see above for behavior if ``high=None``). + size : int or tuple of ints + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + rngstate : encapsulated pointer to rk_state + The specific type depends on the python version. In Python 2 it is + a PyCObject, in Python 3 a PyCapsule object. + + Returns + ------- + out : python scalar or ndarray of np.int32 + `size`-shaped array of random integers from the appropriate + distribution, or a single such random int if `size` not provided. + + """ + cdef npy_uint32 off, rng, buf + cdef npy_uint32 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint32>(high - low) + off = <npy_uint32>(<npy_int32>low) + if size is None: + rk_random_uint32(off, rng, 1, &buf, state) + return <npy_int32>buf + else: + array = <ndarray>np.empty(size, np.int32) + cnt = PyArray_SIZE(array) + out = <npy_uint32 *>PyArray_DATA(array) + with nogil: + rk_random_uint32(off, rng, cnt, out, state) + return array + + +def _rand_int64(low, high, size, rngstate): + """ + _rand_int64(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint64 off, rng, buf + cdef npy_uint64 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint64>(high - low) + off = <npy_uint64>(<npy_int64>low) + if size is None: + rk_random_uint64(off, rng, 1, &buf, state) + return <npy_int64>buf + else: + array = <ndarray>np.empty(size, np.int64) + cnt = PyArray_SIZE(array) + out = <npy_uint64 *>PyArray_DATA(array) + with nogil: + rk_random_uint64(off, rng, cnt, out, state) + return array + +def _rand_uint8(low, high, size, rngstate): + """ + _rand_uint8(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint8 off, rng, buf + cdef npy_uint8 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint8>(high - low) + off = <npy_uint8>(low) + if size is None: + rk_random_uint8(off, rng, 1, &buf, state) + return buf + else: + array = <ndarray>np.empty(size, np.uint8) + cnt = PyArray_SIZE(array) + out = <npy_uint8 *>PyArray_DATA(array) + with nogil: + rk_random_uint8(off, rng, cnt, out, state) + return array + + +def _rand_uint16(low, high, size, rngstate): + """ + _rand_uint16(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint16 off, rng, buf + cdef npy_uint16 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint16>(high - low) + off = <npy_uint16>(low) + if size is None: + rk_random_uint16(off, rng, 1, &buf, state) + return buf + else: + array = <ndarray>np.empty(size, np.uint16) + cnt = PyArray_SIZE(array) + out = <npy_uint16 *>PyArray_DATA(array) + with nogil: + rk_random_uint16(off, rng, cnt, out, state) + return array + + +def _rand_uint32(low, high, size, rngstate): + """ + _rand_uint32(self, low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint32 off, rng, buf + cdef npy_uint32 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint32>(high - low) + off = <npy_uint32>(low) + if size is None: + rk_random_uint32(off, rng, 1, &buf, state) + return <npy_uint32>buf + else: + array = <ndarray>np.empty(size, np.uint32) + cnt = PyArray_SIZE(array) + out = <npy_uint32 *>PyArray_DATA(array) + with nogil: + rk_random_uint32(off, rng, cnt, out, state) + return array + + +def _rand_uint64(low, high, size, rngstate): + """ + _rand_uint64(low, high, size, rngstate) + + See `_rand_int32` for documentation, only the return type changes. + + """ + cdef npy_uint64 off, rng, buf + cdef npy_uint64 *out + cdef ndarray array "arrayObject" + cdef npy_intp cnt + cdef rk_state *state = <rk_state *>NpyCapsule_AsVoidPtr(rngstate) + + rng = <npy_uint64>(high - low) + off = <npy_uint64>(low) + if size is None: + rk_random_uint64(off, rng, 1, &buf, state) + return <npy_uint64>buf + else: + array = <ndarray>np.empty(size, np.uint64) + cnt = PyArray_SIZE(array) + out = <npy_uint64 *>PyArray_DATA(array) + with nogil: + rk_random_uint64(off, rng, cnt, out, state) + return array + +# Look up table for randint functions keyed by type name. The stored data +# is a tuple (lbnd, ubnd, func), where lbnd is the smallest value for the +# type, ubnd is one greater than the largest value, and func is the +# function to call. +_randint_type = { + 'bool': (0, 2, _rand_bool), + 'int8': (-2**7, 2**7, _rand_int8), + 'int16': (-2**15, 2**15, _rand_int16), + 'int32': (-2**31, 2**31, _rand_int32), + 'int64': (-2**63, 2**63, _rand_int64), + 'uint8': (0, 2**8, _rand_uint8), + 'uint16': (0, 2**16, _rand_uint16), + 'uint32': (0, 2**32, _rand_uint32), + 'uint64': (0, 2**64, _rand_uint64) + } + + cdef class RandomState: """ RandomState(seed=None) @@ -613,11 +923,12 @@ cdef class RandomState: """ cdef rk_state *internal_state cdef object lock + cdef object state_address poisson_lam_max = np.iinfo('l').max - np.sqrt(np.iinfo('l').max)*10 def __init__(self, seed=None): self.internal_state = <rk_state*>PyMem_Malloc(sizeof(rk_state)) - + self.state_address = NpyCapsule_FromVoidPtr(self.internal_state, NULL) self.lock = Lock() self.seed(seed) @@ -880,15 +1191,15 @@ cdef class RandomState: """ return disc0_array(self.internal_state, rk_long, size, self.lock) - def randint(self, low, high=None, size=None): + def randint(self, low, high=None, size=None, dtype='l'): """ - randint(low, high=None, size=None) + randint(low, high=None, size=None, dtype='l') Return random integers from `low` (inclusive) to `high` (exclusive). - Return random integers from the "discrete uniform" distribution in the - "half-open" interval [`low`, `high`). If `high` is None (the default), - then results are from [0, `low`). + Return random integers from the "discrete uniform" distribution of + the specified dtype in the "half-open" interval [`low`, `high`). If + `high` is None (the default), then results are from [0, `low`). Parameters ---------- @@ -903,6 +1214,13 @@ cdef class RandomState: Output shape. If the given shape is, e.g., ``(m, n, k)``, then ``m * n * k`` samples are drawn. Default is None, in which case a single value is returned. + dtype : dtype, optional + Desired dtype of the result. All dtypes are determined by their + name, i.e., 'int64', 'int`, etc, so byteorder is not available + and a specific precision may have different C types depending + on the platform. The default value is 'l' (C long). + + .. versionadded:: 1.11.0 Returns ------- @@ -931,14 +1249,24 @@ cdef class RandomState: [3, 2, 2, 0]]) """ - if high is not None and low >= high: - raise ValueError("low >= high") - if high is None: high = low low = 0 - return self.random_integers(low, high - 1, size) + key = np.dtype(dtype).name + if not key in _randint_type: + raise TypeError('Unsupported dtype "%s" for randint' % key) + lowbnd, highbnd, randfunc = _randint_type[key] + + if low < lowbnd: + raise ValueError("low is out of bounds for %s" % (key,)) + if high > highbnd: + raise ValueError("high is out of bounds for %s" % (key,)) + if low >= high: + raise ValueError("low >= high") + + with self.lock: + return randfunc(low, high - 1, size, self.state_address) def bytes(self, npy_intp length): """ @@ -1351,11 +1679,13 @@ cdef class RandomState: """ random_integers(low, high=None, size=None) - Return random integers between `low` and `high`, inclusive. + Random integers of type np.int between `low` and `high`, inclusive. - Return random integers from the "discrete uniform" distribution in the - closed interval [`low`, `high`]. If `high` is None (the default), - then results are from [1, `low`]. + Return random integers of type np.int from the "discrete uniform" + distribution in the closed interval [`low`, `high`]. If `high` is + None (the default), then results are from [1, `low`]. The np.int + type translates to the C long type used by Python 2 for "short" + integers and its precision is platform dependent. Parameters ---------- @@ -1421,37 +1751,13 @@ cdef class RandomState: >>> plt.show() """ - if high is not None and low > high: - raise ValueError("low > high") + if high is None: + high = low + low = 1 - cdef long lo, hi, rv - cdef unsigned long diff - cdef long *array_data - cdef ndarray array "arrayObject" - cdef npy_intp length - cdef npy_intp i + return self.randint(low, high + 1, size=size, dtype='l') - if high is None: - lo = 1 - hi = low - else: - lo = low - hi = high - diff = <unsigned long>hi - <unsigned long>lo - if size is None: - with self.lock: - rv = lo + <long>rk_interval(diff, self. internal_state) - return rv - else: - array = <ndarray>np.empty(size, int) - length = PyArray_SIZE(array) - array_data = <long *>PyArray_DATA(array) - with self.lock, nogil: - for i from 0 <= i < length: - rv = lo + <long>rk_interval(diff, self. internal_state) - array_data[i] = rv - return array # Complicated, continuous distributions: def standard_normal(self, size=None): diff --git a/numpy/random/mtrand/numpy.pxd b/numpy/random/mtrand/numpy.pxd index c54f79c0a..488278d6c 100644 --- a/numpy/random/mtrand/numpy.pxd +++ b/numpy/random/mtrand/numpy.pxd @@ -2,6 +2,12 @@ cdef extern from "numpy/npy_no_deprecated_api.h": pass +cdef extern from "mt_compat.h": + + object NpyCapsule_FromVoidPtr(void *ptr, void (*dtor)(object o)) + void * NpyCapsule_AsVoidPtr(object o) + + cdef extern from "numpy/arrayobject.h": cdef enum NPY_TYPES: @@ -71,7 +77,17 @@ cdef extern from "numpy/arrayobject.h": double real double imag + ctypedef int npy_int ctypedef int npy_intp + ctypedef int npy_int64 + ctypedef int npy_uint64 + ctypedef int npy_int32 + ctypedef int npy_uint32 + ctypedef int npy_int16 + ctypedef int npy_uint16 + ctypedef int npy_int8 + ctypedef int npy_uint8 + ctypedef int npy_bool ctypedef extern class numpy.dtype [object PyArray_Descr]: pass diff --git a/numpy/random/mtrand/randomkit.c b/numpy/random/mtrand/randomkit.c index b18897e2c..3a95efeeb 100644 --- a/numpy/random/mtrand/randomkit.c +++ b/numpy/random/mtrand/randomkit.c @@ -70,6 +70,7 @@ #include <errno.h> #include <limits.h> #include <math.h> +#include <assert.h> #ifdef _WIN32 /* @@ -115,6 +116,10 @@ #include <unistd.h> #endif +/* + * Do not move this include. randomkit.h must be included + * after windows timeb.h is included. + */ #include "randomkit.h" #ifndef RK_DEV_URANDOM @@ -207,7 +212,11 @@ rk_randomseed(rk_state *state) #define UPPER_MASK 0x80000000UL #define LOWER_MASK 0x7fffffffUL -/* Slightly optimised reference implementation of the Mersenne Twister */ +/* + * Slightly optimised reference implementation of the Mersenne Twister + * Note that regardless of the precision of long, only 32 bit random + * integers are produced + */ unsigned long rk_random(rk_state *state) { @@ -240,6 +249,219 @@ rk_random(rk_state *state) return y; } + +/* + * Returns an unsigned 64 bit random integer. + */ +NPY_INLINE static npy_uint64 +rk_uint64(rk_state *state) +{ + npy_uint64 upper = (npy_uint64)rk_random(state) << 32; + npy_uint64 lower = (npy_uint64)rk_random(state); + return upper | lower; +} + + +/* + * Returns an unsigned 32 bit random integer. + */ +NPY_INLINE static npy_uint32 +rk_uint32(rk_state *state) +{ + return (npy_uint32)rk_random(state); +} + + +/* + * Fills an array with cnt random npy_uint64 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +void +rk_random_uint64(npy_uint64 off, npy_uint64 rng, npy_intp cnt, + npy_uint64 *out, rk_state *state) +{ + npy_uint64 val, mask = rng; + npy_intp i; + + if (rng == 0) { + for (i = 0; i < cnt; i++) { + out[i] = off; + } + return; + } + + /* Smallest bit mask >= max */ + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + mask |= mask >> 16; + mask |= mask >> 32; + + for (i = 0; i < cnt; i++) { + if (rng <= 0xffffffffUL) { + while ((val = (rk_uint32(state) & mask)) > rng); + } + else { + while ((val = (rk_uint64(state) & mask)) > rng); + } + out[i] = off + val; + } +} + + +/* + * Fills an array with cnt random npy_uint32 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +void +rk_random_uint32(npy_uint32 off, npy_uint32 rng, npy_intp cnt, + npy_uint32 *out, rk_state *state) +{ + npy_uint32 val, mask = rng; + npy_intp i; + + if (rng == 0) { + for (i = 0; i < cnt; i++) { + out[i] = off; + } + return; + } + + /* Smallest bit mask >= max */ + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + mask |= mask >> 16; + + for (i = 0; i < cnt; i++) { + while ((val = (rk_uint32(state) & mask)) > rng); + out[i] = off + val; + } +} + + +/* + * Fills an array with cnt random npy_uint16 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +void +rk_random_uint16(npy_uint16 off, npy_uint16 rng, npy_intp cnt, + npy_uint16 *out, rk_state *state) +{ + npy_uint16 val, mask = rng; + npy_intp i; + npy_uint32 buf; + int bcnt = 0; + + if (rng == 0) { + for (i = 0; i < cnt; i++) { + out[i] = off; + } + return; + } + + /* Smallest bit mask >= max */ + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + + for (i = 0; i < cnt; i++) { + do { + if (!bcnt) { + buf = rk_uint32(state); + bcnt = 1; + } + else { + buf >>= 16; + bcnt--; + } + val = (npy_uint16)buf & mask; + } while (val > rng); + out[i] = off + val; + } +} + + +/* + * Fills an array with cnt random npy_uint8 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +void +rk_random_uint8(npy_uint8 off, npy_uint8 rng, npy_intp cnt, + npy_uint8 *out, rk_state *state) +{ + npy_uint8 val, mask = rng; + npy_intp i; + npy_uint32 buf; + int bcnt = 0; + + if (rng == 0) { + for (i = 0; i < cnt; i++) { + out[i] = off; + } + return; + } + + /* Smallest bit mask >= max */ + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + + for (i = 0; i < cnt; i++) { + do { + if (!bcnt) { + buf = rk_uint32(state); + bcnt = 3; + } + else { + buf >>= 8; + bcnt--; + } + val = (npy_uint8)buf & mask; + } while (val > rng); + out[i] = off + val; + } +} + + +/* + * Fills an array with cnt random npy_bool between off and off + rng + * inclusive. + */ +void +rk_random_bool(npy_bool off, npy_bool rng, npy_intp cnt, + npy_bool *out, rk_state *state) +{ + npy_intp i; + npy_uint32 buf; + int bcnt = 0; + + if (rng == 0) { + for (i = 0; i < cnt; i++) { + out[i] = off; + } + return; + } + + /* If we reach here rng and mask are one and off is zero */ + assert(rng == 1 && off == 0); + for (i = 0; i < cnt; i++) { + if (!bcnt) { + buf = rk_uint32(state); + bcnt = 31; + } + else { + buf >>= 1; + bcnt--; + } + out[i] = (buf & 0x00000001) != 0; + } +} + + long rk_long(rk_state *state) { diff --git a/numpy/random/mtrand/randomkit.h b/numpy/random/mtrand/randomkit.h index e049488ee..fcdd606a1 100644 --- a/numpy/random/mtrand/randomkit.h +++ b/numpy/random/mtrand/randomkit.h @@ -56,11 +56,13 @@ * defaults to "/dev/urandom" */ -#include <stddef.h> - #ifndef _RANDOMKIT_ #define _RANDOMKIT_ +#include <stddef.h> +#include <numpy/npy_common.h> + + #define RK_STATE_LEN 624 typedef struct rk_state_ @@ -149,6 +151,41 @@ extern unsigned long rk_ulong(rk_state *state); extern unsigned long rk_interval(unsigned long max, rk_state *state); /* + * Fills an array with cnt random npy_uint64 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +extern void rk_random_uint64(npy_uint64 off, npy_uint64 rng, npy_intp cnt, + npy_uint64 *out, rk_state *state); + +/* + * Fills an array with cnt random npy_uint32 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +extern void rk_random_uint32(npy_uint32 off, npy_uint32 rng, npy_intp cnt, + npy_uint32 *out, rk_state *state); + +/* + * Fills an array with cnt random npy_uint16 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +extern void rk_random_uint16(npy_uint16 off, npy_uint16 rng, npy_intp cnt, + npy_uint16 *out, rk_state *state); + +/* + * Fills an array with cnt random npy_uint8 between off and off + rng + * inclusive. The numbers wrap if rng is sufficiently large. + */ +extern void rk_random_uint8(npy_uint8 off, npy_uint8 rng, npy_intp cnt, + npy_uint8 *out, rk_state *state); + +/* + * Fills an array with cnt random npy_bool between off and off + rng + * inclusive. It is assumed tha npy_bool as the same size as npy_uint8. + */ +extern void rk_random_bool(npy_bool off, npy_bool rng, npy_intp cnt, + npy_bool *out, rk_state *state); + +/* * Returns a random double between 0.0 and 1.0, 1.0 excluded. */ extern double rk_double(rk_state *state); diff --git a/numpy/random/tests/test_random.py b/numpy/random/tests/test_random.py index c3aa43f0e..a6783fe8f 100644 --- a/numpy/random/tests/test_random.py +++ b/numpy/random/tests/test_random.py @@ -128,6 +128,83 @@ class TestSetState(TestCase): # arguments without truncation. self.prng.negative_binomial(0.5, 0.5) +class TestRandint(TestCase): + + rfunc = np.random.randint + + # valid integer/boolean types + itype = [np.bool, np.int8, np.uint8, np.int16, np.uint16, + np.int32, np.uint32, np.int64, np.uint64] + + def test_unsupported_type(self): + assert_raises(TypeError, self.rfunc, 1, dtype=np.float) + + def test_bounds_checking(self): + for dt in self.itype: + lbnd = 0 if dt is np.bool else np.iinfo(dt).min + ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1 + assert_raises(ValueError, self.rfunc, lbnd - 1 , ubnd, dtype=dt) + assert_raises(ValueError, self.rfunc, lbnd , ubnd + 1, dtype=dt) + assert_raises(ValueError, self.rfunc, ubnd , lbnd, dtype=dt) + assert_raises(ValueError, self.rfunc, 1 , 0, dtype=dt) + + def test_rng_zero_and_extremes(self): + for dt in self.itype: + lbnd = 0 if dt is np.bool else np.iinfo(dt).min + ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1 + tgt = ubnd - 1 + assert_equal(self.rfunc(tgt, tgt + 1, size=1000, dtype=dt), tgt) + tgt = lbnd + assert_equal(self.rfunc(tgt, tgt + 1, size=1000, dtype=dt), tgt) + tgt = (lbnd + ubnd)//2 + assert_equal(self.rfunc(tgt, tgt + 1, size=1000, dtype=dt), tgt) + + def test_in_bounds_fuzz(self): + # Don't use fixed seed + np.random.seed() + for dt in self.itype[1:]: + for ubnd in [4, 8, 16]: + vals = self.rfunc(2, ubnd, size=2**16, dtype=dt) + assert_(vals.max() < ubnd) + assert_(vals.min() >= 2) + vals = self.rfunc(0, 2, size=2**16, dtype=np.bool) + assert_(vals.max() < 2) + assert_(vals.min() >= 0) + + def test_repeatability(self): + import hashlib + # We use a md5 hash of generated sequences of 1000 samples + # in the range [0, 6) for all but np.bool, where the range + # is [0, 2). Hashes are for little endian numbers. + tgt = {'bool': '7dd3170d7aa461d201a65f8bcf3944b0', + 'int16': '1b7741b80964bb190c50d541dca1cac1', + 'int32': '4dc9fcc2b395577ebb51793e58ed1a05', + 'int64': '17db902806f448331b5a758d7d2ee672', + 'int8': '27dd30c4e08a797063dffac2490b0be6', + 'uint16': '1b7741b80964bb190c50d541dca1cac1', + 'uint32': '4dc9fcc2b395577ebb51793e58ed1a05', + 'uint64': '17db902806f448331b5a758d7d2ee672', + 'uint8': '27dd30c4e08a797063dffac2490b0be6'} + + for dt in self.itype[1:]: + np.random.seed(1234) + + # view as little endian for hash + if sys.byteorder == 'little': + val = self.rfunc(0, 6, size=1000, dtype=dt) + else: + val = self.rfunc(0, 6, size=1000, dtype=dt).byteswap() + + res = hashlib.md5(val.view(np.int8)).hexdigest() + assert_(tgt[np.dtype(dt).name] == res) + + # bools do not depend on endianess + np.random.seed(1234) + val = self.rfunc(0, 2, size=1000, dtype=np.bool).view(np.int8) + res = hashlib.md5(val).hexdigest() + assert_(tgt[np.dtype(np.bool).name] == res) + + class TestRandomDist(TestCase): # Make sure the random distribution returns the correct value for a # given seed diff --git a/tools/travis-test.sh b/tools/travis-test.sh index 939594d8c..40e266b26 100755 --- a/tools/travis-test.sh +++ b/tools/travis-test.sh @@ -50,8 +50,10 @@ setup_base() | grep -E "warning\>" \ | tee warnings # Check for an acceptable number of warnings. Some warnings are out of - # our control, so adjust the number as needed. - [[ $(wc -l < warnings) -lt 1 ]] + # our control, so adjust the number as needed. At the moment a + # cython generated code produces a warning about '-2147483648L', but + # the code seems to compile OK. + [[ $(wc -l < warnings) -lt 2 ]] fi else sysflags="$($PYTHON -c "from distutils import sysconfig; \ |