diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e266e5..b05a04e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated the `randint_untyped` implementation, which now supports `array_like` (broadcastable) `low`/`high` bounds [gh-176](https://github.com/IntelPython/mkl_random/pull/176) * Sped up `randint` with `array_like` bounds by generating words in cache-sized chunks and by picking the Lemire rejection test by range width [gh-173](https://github.com/IntelPython/mkl_random/pull/173) * Reduced the memory use and sped up integer generation with `WH`, `MCG31`, `R250` and `MRG32K3A`, which lack `viRngUniformBits` support [gh-178](https://github.com/IntelPython/mkl_random/pull/178) +* Replaced the `mklrand_py_helper.h` shim with `cpython.bytes` cimports and dropped the no-op `__future__` import, since the project requires Python 3.10+ [gh-192](https://github.com/IntelPython/mkl_random/pull/192) ### Fixed * Fixed `multinormal_cholesky` and the other functions taking `method` routing integer-like ids such as `np.int64(0)`, `np.int64(2)` or `False` to `BoxMuller`; NumPy integers now select the method they name [gh-189](https://github.com/IntelPython/mkl_random/pull/189) @@ -45,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * 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) * 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) * Fixed `multinormal_cholesky` silently returning wrong output, e.g. every sample equal to `mean`, when the number of samples times the dimension exceeds `MKL_INT_MAX` [gh-187](https://github.com/IntelPython/mkl_random/pull/187) +* Fixed the `bytes` docstring, which documented a `str` return type instead of `bytes` [gh-192](https://github.com/IntelPython/mkl_random/pull/192) +* Fixed the `tomaxint` example, which compared against the Python 2 `sys.maxint` [gh-192](https://github.com/IntelPython/mkl_random/pull/192) +* Fixed the remaining Python 2 references in the `random_sample`, `random_integers` and `randint` docstrings and in the `zipf` implementation comment: the `` reprs, `np.int` and `sys.maxint` [gh-192](https://github.com/IntelPython/mkl_random/pull/192) +* Fixed the `random_integers` docstring, which described the result as `numpy.int_` rather than a C `long`, documented the wrong scalar return type, and passed a float `size` that no longer works [gh-192](https://github.com/IntelPython/mkl_random/pull/192) ### Removed * 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) diff --git a/mkl_random/__init__.py b/mkl_random/__init__.py index a76adc4..2ba26d3 100644 --- a/mkl_random/__init__.py +++ b/mkl_random/__init__.py @@ -23,8 +23,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from __future__ import absolute_import, division, print_function - from . import _init_helper from ._version import __version__ from .mklrand import ( diff --git a/mkl_random/mklrand.pyx b/mkl_random/mklrand.pyx index cfec512..7f55da6 100644 --- a/mkl_random/mklrand.pyx +++ b/mkl_random/mklrand.pyx @@ -38,6 +38,11 @@ cdef extern from "numpy/npy_no_deprecated_api.h": cimport cpython.tuple cimport cython cimport numpy as cnp +from cpython.bytes cimport ( + PyBytes_AS_STRING, + PyBytes_Check, + PyBytes_FromStringAndSize, +) from libc.stdint cimport int64_t from libc.string cimport memcpy, memset @@ -48,11 +53,6 @@ cdef extern from "math.h": cdef extern from "numpy/npy_math.h": int npy_isfinite(double x) -cdef extern from "mklrand_py_helper.h": - object empty_py_bytes(cnp.npy_intp length, void **bytesVec) - char* py_bytes_DataPtr(object b) - int is_bytes_object(object b) - cdef extern from "numpy_multiiter_workaround.h": cnp.npy_intp cnp_PyArray_MultiIter_SIZE \ "workaround_PyArray_MultiIter_SIZE"(cnp.broadcast multi) nogil @@ -1950,7 +1950,7 @@ cdef class _MKLRandomState: cdef int state_buffer_size = 0 cdef int cur_size cdef int brng_id - cdef void *bytesPtr + cdef char *bytesPtr = NULL bytestring = None # Reseed can change the size; grow until the buffer fits, then save @@ -1959,10 +1959,11 @@ cdef class _MKLRandomState: cur_size = irk_get_stream_size(self.internal_state) if cur_size <= state_buffer_size: brng_id = irk_get_brng_mkl(self.internal_state) - irk_get_state_mkl(self.internal_state, bytesPtr) + irk_get_state_mkl(self.internal_state, bytesPtr) break state_buffer_size = cur_size - bytestring = empty_py_bytes(state_buffer_size, &bytesPtr) + bytestring = PyBytes_FromStringAndSize(NULL, state_buffer_size) + bytesPtr = PyBytes_AS_STRING(bytestring) if cur_size != state_buffer_size: bytestring = bytestring[:cur_size] @@ -2022,7 +2023,7 @@ cdef class _MKLRandomState: """ cdef char *bytes_ptr - cdef int err + cdef int err = 0 cdef cnp.ndarray obj "arrayObject_obj" if isinstance(state, dict): @@ -2070,10 +2071,11 @@ cdef class _MKLRandomState: expected_brng = _brng_dict[algorithm_name] stream_buf = state[1] - if not is_bytes_object(stream_buf): + # NB: the module-level `bytes` alias shadows the builtin here + if not PyBytes_Check(stream_buf): raise ValueError("state is expected to be bytes") - bytes_ptr = py_bytes_DataPtr(stream_buf) + bytes_ptr = PyBytes_AS_STRING(stream_buf) with self.lock: err = irk_set_state_mkl( @@ -2124,7 +2126,7 @@ cdef class _MKLRandomState: >>> mkl_random.random_sample() 0.47108547995356098 >>> type(mkl_random.random_sample()) - + >>> mkl_random.random_sample((5,)) array([ 0.30220482, 0.86820401, 0.1654503 , 0.11659149, 0.54323428]) @@ -2583,7 +2585,7 @@ cdef class _MKLRandomState: 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 'np.int'. + on the platform. The default value is long. .. versionadded:: 1.11.0 @@ -2694,19 +2696,20 @@ cdef class _MKLRandomState: Returns ------- - out : str - String of length `length`. + out : bytes + Bytes of length `length`. Examples -------- >>> mkl_random.bytes(10) - ' eh\\x85\\x022SZ\\xbf\\xa4' #random + b' eh\\x85\\x022SZ\\xbf\\xa4' #random """ - cdef void *bytes - bytestring = empty_py_bytes(length, &bytes) + cdef char *bytes_ptr + bytestring = PyBytes_FromStringAndSize(NULL, length) + bytes_ptr = PyBytes_AS_STRING(bytestring) with self.lock, nogil: - irk_fill(bytes, length, self.internal_state) + irk_fill(bytes_ptr, length, self.internal_state) return bytestring def choice(self, a, size=None, replace=True, p=None): @@ -3125,13 +3128,12 @@ cdef class _MKLRandomState: """ random_integers(low, high=None, size=None) - Random integers of type np.int between `low` and `high`, inclusive. + Random integers of C `long` type between `low` and `high`, inclusive. - Return random integers of type np.int from the "discrete uniform" + Return random integers of C `long` type 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. + None (the default), then results are from [1, `low`]. The precision + of C `long` is platform dependent. This function has been deprecated. Use randint instead. @@ -3173,11 +3175,11 @@ cdef class _MKLRandomState: Examples -------- >>> mkl_random.random_integers(5) - 4 + 4 # random >>> type(mkl_random.random_integers(5)) - - >>> mkl_random.random_integers(5, size=(3.,2.)) - array([[5, 4], + + >>> mkl_random.random_integers(5, size=(3,2)) + array([[5, 4], # random [3, 3], [4, 5]]) @@ -3606,18 +3608,16 @@ cdef class _MKLRandomState: -------- >>> RS = mkl_random.MKLRandomState() # need a MKLRandomState object >>> RS.tomaxint((2,2,2)) - array([[[1170048599, 1600360186], + array([[[1170048599, 1600360186], # random [ 739731006, 1947757578]], [[1871712945, 752307660], [1601631370, 1479324245]]]) - >>> import sys - >>> sys.maxint - 2147483647 - >>> RS.tomaxint((2,2,2)) < sys.maxint + >>> import numpy as np + >>> RS.tomaxint((2,2,2)) < np.iinfo("long").max array([[[ True, True], [ True, True]], [[ True, True], - [ True, True]]], dtype=bool) + [ True, True]]]) """ return vec_long_disc0_array( diff --git a/mkl_random/src/mkl_distributions.cpp b/mkl_random/src/mkl_distributions.cpp index 9320ad7..81c6f0e 100644 --- a/mkl_random/src/mkl_distributions.cpp +++ b/mkl_random/src/mkl_distributions.cpp @@ -1507,10 +1507,10 @@ void irk_zipf_long_vec(irk_state *state, V = Vvec[i]; X = (long)floor(pow(U, (-1.0) / am1)); /* The real result may be above what can be represented in a signed - * long. It will get casted to -sys.maxint-1. Since this is + * long. It will get casted to LONG_MIN. Since this is * a straightforward rejection algorithm, we can just reject this * value in the rejection condition below. This function then models - * a Zipf distribution truncated to sys.maxint. + * a Zipf distribution truncated to LONG_MAX. */ T = pow(d_one + d_one / X, am1); if ((X > 0) && ((V * X) * (T - d_one) / (b - d_one) <= T / b)) { diff --git a/mkl_random/src/mklrand_py_helper.h b/mkl_random/src/mklrand_py_helper.h deleted file mode 100644 index 9086096..0000000 --- a/mkl_random/src/mklrand_py_helper.h +++ /dev/null @@ -1,43 +0,0 @@ -// clang-format off -#ifndef _MKLRAND_PY_HELPER_H_ -#define _MKLRAND_PY_HELPER_H_ - -#include -// clang-format on - -static PyObject *empty_py_bytes(npy_intp length, void **bytesVec) -{ - PyObject *b; -#if PY_MAJOR_VERSION >= 3 - b = PyBytes_FromStringAndSize(NULL, length); - if (b) { - *bytesVec = PyBytes_AS_STRING(b); - } -#else - b = PyString_FromStringAndSize(NULL, length); - if (b) { - *bytesVec = PyString_AS_STRING(b); - } -#endif - return b; -} - -static char *py_bytes_DataPtr(PyObject *b) -{ -#if PY_MAJOR_VERSION >= 3 - return PyBytes_AS_STRING(b); -#else - return PyString_AS_STRING(b); -#endif -} - -static int is_bytes_object(PyObject *b) -{ -#if PY_MAJOR_VERSION >= 3 - return PyBytes_Check(b); -#else - return PyString_Check(b); -#endif -} - -#endif /* _MKLRAND_PY_HELPER_H_ */