From 281f2fc70febfa24eb5a096e2a0b65204a363b78 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Sun, 22 Sep 2024 23:48:51 +0100 Subject: [PATCH 1/4] DEPR: Restore and deprecate mode --- randomgen/_deprecated_value.py | 20 ++++++++++++++++++++ randomgen/common.pyx | 17 ++++++++++++++--- randomgen/lxm.pyx | 2 +- randomgen/mt19937.pyx | 17 +++++++++++++---- randomgen/pcg64.pyx | 29 +++++++++++++++++++++++++---- randomgen/philox.pyx | 26 ++++++++++++++++++-------- randomgen/romu.pyx | 2 +- randomgen/sfc.pyx | 18 +++++++++++++++--- 8 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 randomgen/_deprecated_value.py diff --git a/randomgen/_deprecated_value.py b/randomgen/_deprecated_value.py new file mode 100644 index 000000000..3b68226c1 --- /dev/null +++ b/randomgen/_deprecated_value.py @@ -0,0 +1,20 @@ +class _DeprecatedValueType: + """Special keyword value for deprecated arguments.. + + The instance of this class may be used as the default value assigned to a + keyword if the parameter is deprecated. + """ + __instance = None + def __new__(cls): + # ensure that only one instance exists + if not cls.__instance: + cls.__instance = super().__new__(cls) + return cls.__instance + + def __repr__(self): + return "" + + +_DeprecatedValue = _DeprecatedValueType() + +__all__ = ["_DeprecatedValue"] diff --git a/randomgen/common.pyx b/randomgen/common.pyx index b8ec6c080..704706eeb 100644 --- a/randomgen/common.pyx +++ b/randomgen/common.pyx @@ -1,5 +1,6 @@ #!python import sys +import warnings from collections import namedtuple try: from threading import Lock @@ -14,6 +15,7 @@ cimport numpy as np from randomgen.common cimport * from randomgen cimport api from randomgen.seed_sequence import ISeedSequence +from randomgen._deprecated_value import _DeprecatedValue ISEED_SEQUENCES = (ISeedSequence,) # NumPy 1.17 @@ -43,14 +45,23 @@ cdef class BitGenerator(_BitGenerator): """ Abstract class for all BitGenerators """ - def __init__(self, seed, mode="sequence"): - if mode is not None and (not isinstance(mode, str) or mode.lower() not in self._supported_modes()): + def __init__(self, seed, *, numpy_seed=False, mode=_DeprecatedValue): + if mode is not _DeprecatedValue: + msg = ("mode is deprecated and will be removed in a future version. " + "Seeding defaults to a numpy.random.SeedSequence instance.") + if "numpy" in self._supported_modes(): + msg += "Use numpy_seed=True to enforce numpy-compatible seeding." + warnings.warn(msg, FutureWarning) + if mode is not _DeprecatedValue and ( + not isinstance(mode, str) or mode.lower() not in self._supported_modes() + ): if len(self._supported_modes()) == 1: msg = f"mode must be {self._supported_modes()[0]}" else: modes = ", ".join(f"\"{mode}\"" for mode in self._supported_modes()) raise ValueError(f"mode must be one of: {modes}.") - self.mode = mode.lower() + mode = mode.lower() if isinstance(mode, str) else mode + self.mode = "numpy" if (numpy_seed or mode == "numpy") else "sequence" super().__init__(seed) def _supported_modes(self): diff --git a/randomgen/lxm.pyx b/randomgen/lxm.pyx index 014d06243..a7fdd232b 100644 --- a/randomgen/lxm.pyx +++ b/randomgen/lxm.pyx @@ -130,7 +130,7 @@ cdef class LXM(BitGenerator): https://nuclear.llnl.gov/CNP/rng/rngman/node4.html. """ def __init__(self, seed=None, *, b=3037000493): - BitGenerator.__init__(self, seed, "sequence") + BitGenerator.__init__(self, seed) self.seed(seed) self.rng_state.b = b | 1 diff --git a/randomgen/mt19937.pyx b/randomgen/mt19937.pyx index ba3f79397..c6d03fd6d 100644 --- a/randomgen/mt19937.pyx +++ b/randomgen/mt19937.pyx @@ -6,7 +6,7 @@ import operator import numpy as np cimport numpy as np - +from randomgen._deprecated_value import _DeprecatedValue from randomgen.common cimport * __all__ = ["MT19937"] @@ -25,7 +25,7 @@ cdef uint64_t mt19937_raw(void *st) noexcept nogil: cdef class MT19937(BitGenerator): """ - MT19937(seed=None, *, mode="sequence") + MT19937(seed=None, *, numpy_seed=False, mode=_DeprecatedValue) Container for the Mersenne Twister pseudo-random number generator. @@ -39,11 +39,20 @@ cdef class MT19937(BitGenerator): unsigned integers are read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + numpy_seed : bool + Set to True to use the same seeding mechanism as NumPy and + so matches NumPy exactly. + + .. versionadded: 2.0.0 + mode : {None, "sequence", "numpy"}, optional "sequence" uses a SeedSequence to transforms the seed into an initial state. None defaults to "sequence". "numpy" uses the same seeding mechanism as NumPy and so matches NumPy exactly. + .. deprecated: 2.0.0 + mode is deprecated. Use numpy_seed tp enforce numpy-matching seeding + Attributes ---------- lock : threading.Lock @@ -112,8 +121,8 @@ cdef class MT19937(BitGenerator): No. 3, Summer 2008, pp. 385-390. """ - def __init__(self, seed=None, *, mode="sequence"): - BitGenerator.__init__(self, seed, mode) + def __init__(self, seed=None, *, numpy_seed=False, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, numpy_seed=numpy_seed, mode=mode) self.seed(seed) self._bitgen.state = &self.rng_state self._bitgen.next_uint64 = &mt19937_uint64 diff --git a/randomgen/pcg64.pyx b/randomgen/pcg64.pyx index 7351de570..f25d3565b 100644 --- a/randomgen/pcg64.pyx +++ b/randomgen/pcg64.pyx @@ -5,6 +5,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["PCG64", "LCG128Mix", "PCG64DXSM"] @@ -42,7 +43,7 @@ cdef double lcg128mix_double(void* st) noexcept nogil: cdef class PCG64(BitGenerator): """ - PCG64(seed=None, inc=0, *, variant="xsl-rr", mode="sequence") + PCG64(seed=None, inc=0, *, variant="xsl-rr", numpy_seed=False, mode=_DeprecatedValue) Container for the PCG-64 pseudo-random number generator. @@ -65,6 +66,13 @@ cdef class PCG64(BitGenerator): returns the value before advancing the state. This variant is PCG64 2.0. "cm-dxsm" (cheap multiplier-dxsm), 2 and "2.0" are aliases for "dxsm". None trusts randomgen to chose the variant. + numpy_seed : bool + Set to True to use the same seeding mechanism as NumPy and + so matches NumPy exactly. To match NumPy, variant must be ``xsl-rr``. + When using "numpy", ``inc`` must be ``None``. + + .. versionadded: 2.0.0 + mode : {None, "sequence", "numpy"}, optional "sequence" uses a SeedSequence to transforms the seed into an initial state. "numpy" also uses a SeedSequence but seeds the generator in a @@ -72,6 +80,10 @@ cdef class PCG64(BitGenerator): ``None``. Additionally, to match NumPy, variant must be ``xsl-rr`` (this is not checked). + .. deprecated: 2.0.0 + mode is deprecated. Use numpy_seed tp enforce numpy-matching seeding + + Attributes ---------- lock : threading.Lock @@ -145,8 +157,9 @@ cdef class PCG64(BitGenerator): .. [2] O'Neill, Melissa E. "PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation" """ - def __init__(self, seed=None, inc=None, *, variant="xsl-rr", mode="sequence"): - BitGenerator.__init__(self, seed, mode) + def __init__(self, seed=None, inc=None, *, variant="xsl-rr", numpy_seed=False, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode, numpy_seed=numpy_seed) + self.rng_state.pcg_state = PyArray_malloc_aligned(sizeof(pcg64_random_t)) inc = None if seed is None else inc @@ -157,6 +170,14 @@ cdef class PCG64(BitGenerator): variant = variant.lower().replace("-", "") if variant not in ("xslrr", "1.0", "1", "cmdxsm", "dxsm", "dxsm128", "2.0", "2"): raise ValueError(f"variant {orig_variant} is not known.") + if self.mode == "numpy": + if orig_variant != "xsl-rr": + raise ValueError( + f"variant must be 'xsl-rr' when using numpy-matching seeding. Got '{orig_variant}'" + ) + if inc is not None: + raise ValueError("inc much be none when using numpy-matching seeding.") + self.variant = variant self._setup_rng_state() self.seed(seed, inc) @@ -605,7 +626,7 @@ cdef class LCG128Mix(BitGenerator): "lower": 4} self._inv_output_lookup = {v: k for k, v in self._output_lookup.items()} self._cfunc = None - BitGenerator.__init__(self, seed, "sequence") + BitGenerator.__init__(self, seed) self.rng_state.pcg_state = PyArray_malloc_aligned(sizeof(lcg128mix_random_t)) if hasattr(output, "argtypes") and hasattr(output, "restype"): from ctypes import c_ulonglong diff --git a/randomgen/philox.pyx b/randomgen/philox.pyx index 251d95269..81b524016 100644 --- a/randomgen/philox.pyx +++ b/randomgen/philox.pyx @@ -4,7 +4,7 @@ import numpy as np from randomgen.common cimport * - +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["Philox"] @@ -45,7 +45,7 @@ cdef uint64_t philox2x32_raw(void *st) noexcept nogil: cdef class Philox(BitGenerator): """ - Philox(seed=None, *, counter=None, key=None, number=4, width=64, mode="sequence") + Philox(seed=None, *, counter=None, key=None, number=4, width=64, *, numpy_seed=False, mode=_DeprecatedValue) Container for the Philox family of pseudo-random number generators. @@ -76,12 +76,22 @@ cdef class Philox(BitGenerator): width : {32, 64}, optional Bit width the values produced. Maps to W in the Philox variant naming scheme PhiloxNxW. + numpy_seed : bool + Set to True to use the same seeding mechanism as NumPy and + so matches NumPy exactly. To match NumPy, variant must be ``xsl-rr``. + When using "numpy", ``inc`` must be ``None``. + + .. versionadded: 2.0.0 + mode : {None, "sequence", "numpy"}, optional "sequence" uses a SeedSequence to transforms the seed into an initial - state. None defaults to "sequence". Using "numpy" ensures that the - generator is configurated using the same parameters required to - produce the same sequence that is realized in NumPy, for a given - ``SeedSequence``. + state. "numpy" also uses a SeedSequence but seeds the generator in a + way that is identical to NumPy. When using "numpy", ``inc`` must be + ``None``. Additionally, to match NumPy, variant must be ``xsl-rr`` + (this is not checked). + + .. deprecated: 2.0.0 + mode is deprecated. Use numpy_seed tp enforce numpy-matching seeding Attributes ---------- @@ -168,8 +178,8 @@ cdef class Philox(BitGenerator): cdef int w def __init__(self, seed=None, *, counter=None, key=None, number=4, - width=64, mode="sequence"): - BitGenerator.__init__(self, seed, mode) + width=64, numpy_seed=False, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode, numpy_seed=numpy_seed) if number not in (2, 4): raise ValueError("number must be either 2 or 4") if width not in (32, 64): diff --git a/randomgen/romu.pyx b/randomgen/romu.pyx index e0c686e48..0b9eb4571 100644 --- a/randomgen/romu.pyx +++ b/randomgen/romu.pyx @@ -100,7 +100,7 @@ cdef class Romu(BitGenerator): def __init__(self, seed=None, variant="quad"): self.variant = self._check_variant(variant) - BitGenerator.__init__(self, seed, "sequence") + BitGenerator.__init__(self, seed) self.seed(seed) self._bitgen.state = &self.rng_state diff --git a/randomgen/sfc.pyx b/randomgen/sfc.pyx index 579574dd4..9b556cb34 100644 --- a/randomgen/sfc.pyx +++ b/randomgen/sfc.pyx @@ -8,6 +8,7 @@ cimport numpy as np cimport cython from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["SFC64"] @@ -37,7 +38,7 @@ cdef uint64_t choosek(uint64_t k): cdef class SFC64(BitGenerator): """ - SFC64(seed=None, w=1, k=1, *, mode="sequence") + SFC64(seed=None, w=1, k=1, *, numpy_seed=False, mode=_DeprecatedValue) Chris Doty-Humphrey's Small Fast Chaotic PRNG with optional Weyl Sequence @@ -55,11 +56,22 @@ cdef class SFC64(BitGenerator): k : {uint64, None}, default 1 The increment to the Weyl sequence. Must be odd, and if even, 1 is added. If None, then `k` `is generated from the `SeedSequence`. + numpy_seed : bool + Set to True to use the same seeding mechanism as NumPy. + Uses the seed sequence to initialize three state values and + checks that both w and k are 1. + + .. versionadded: 2.0.0 + mode : {"sequence", "numpy"} The default uses a seed sequence to initialize all unspecified values. When using "numpy" uses the seed sequence to initialize three values and checks that both w and k are 1. + .. deprecated: 2.0.0 + mode is deprecated. Use numpy_seed tp enforce numpy-matching seeding + + Notes ----- ``SFC64`` is a 256-bit implementation of Chris Doty-Humphrey's Small Fast @@ -111,8 +123,8 @@ cdef class SFC64(BitGenerator): _seed_seq_len = 4 _seed_seq_dtype = np.uint64 - def __init__(self, seed=None, w=1, k=1, *, mode="sequence"): - BitGenerator.__init__(self, seed, mode) + def __init__(self, seed=None, w=1, k=1, *, numpy_seed=False, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, numpy_seed=numpy_seed, mode=mode) self.w = w self.k = k self.seed(seed) From 295a19649447d6e471b33848658bcb2a4ade1713 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 23 Sep 2024 11:39:18 +0100 Subject: [PATCH 2/4] REF: Complete mode restoration with deprecation --- randomgen/_deprecated_value.py | 2 + randomgen/aes.pyx | 14 +++-- randomgen/chacha.pyx | 14 +++-- randomgen/common.pyx | 2 +- randomgen/dsfmt.pyx | 14 +++-- randomgen/hc128.pyx | 13 +++-- randomgen/jsf.pyx | 14 +++-- randomgen/mt19937.pyx | 6 ++- randomgen/mt64.pyx | 14 +++-- randomgen/pcg32.pyx | 13 +++-- randomgen/pcg64.pyx | 3 +- randomgen/philox.pyx | 3 +- randomgen/sfmt.pyx | 13 +++-- randomgen/speck128.pyx | 13 +++-- .../test_bit_generators_against_numpy.py | 8 +-- randomgen/tests/test_direct.py | 53 +++++++++---------- randomgen/tests/test_extended_generator.py | 32 +++++------ randomgen/tests/test_lcg128mix_pcg64dxsm.py | 4 +- randomgen/tests/test_wrapper.py | 4 +- randomgen/threefry.pyx | 13 +++-- randomgen/xoroshiro128.pyx | 14 +++-- randomgen/xorshift1024.pyx | 14 +++-- randomgen/xoshiro256.pyx | 14 +++-- randomgen/xoshiro512.pyx | 15 ++++-- setup.cfg | 1 + 25 files changed, 205 insertions(+), 105 deletions(-) diff --git a/randomgen/_deprecated_value.py b/randomgen/_deprecated_value.py index 3b68226c1..a6957c4c0 100644 --- a/randomgen/_deprecated_value.py +++ b/randomgen/_deprecated_value.py @@ -4,7 +4,9 @@ class _DeprecatedValueType: The instance of this class may be used as the default value assigned to a keyword if the parameter is deprecated. """ + __instance = None + def __new__(cls): # ensure that only one instance exists if not cls.__instance: diff --git a/randomgen/aes.pyx b/randomgen/aes.pyx index 1f2ffc2bb..e909af257 100644 --- a/randomgen/aes.pyx +++ b/randomgen/aes.pyx @@ -2,7 +2,7 @@ import numpy as np from randomgen.common cimport * - +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["AESCounter"] @@ -17,7 +17,7 @@ cdef double aes_double(void* st) noexcept nogil: cdef class AESCounter(BitGenerator): """ - AESCounter(seed=None, *, counter=None, key=None) + AESCounter(seed=None, *, counter=None, key=None, mode=) Container for the AES Counter pseudo-random number generator. @@ -38,6 +38,12 @@ cdef class AESCounter(BitGenerator): another RNG before use, the value in key is directly set. Can be either a Python int in [0, 2**128) or a 2-element uint64 array. key and seed cannot both be used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -123,8 +129,8 @@ cdef class AESCounter(BitGenerator): .. [1] Advanced Encryption Standard. (n.d.). In Wikipedia. Retrieved June 1, 2019, from https://en.wikipedia.org/wiki/Advanced_Encryption_Standard """ - def __init__(self, seed=None, *, counter=None, key=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, counter=None, key=None, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) # Calloc since ctr needs to be 0 self.rng_state = PyArray_calloc_aligned( sizeof(aesctr_state_t), 1 diff --git a/randomgen/chacha.pyx b/randomgen/chacha.pyx index 761807205..ad4285a63 100644 --- a/randomgen/chacha.pyx +++ b/randomgen/chacha.pyx @@ -2,7 +2,7 @@ import numpy as np from randomgen.common cimport * - +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["ChaCha"] cdef uint64_t chacha_uint64(void* st) noexcept nogil: @@ -16,7 +16,7 @@ cdef double chacha_double(void* st) noexcept nogil: cdef class ChaCha(BitGenerator): """ - ChaCha(seed=None, *, counter=None, key=None, rounds=20) + ChaCha(seed=None, *, counter=None, key=None, rounds=20, mode=) Container for the ChaCha family of Counter pseudo-random number generators @@ -43,6 +43,12 @@ cdef class ChaCha(BitGenerator): The standard number of rounds in 20. Smaller values, usually 8 or more, can be used to reduce security properties of the random stream while improving performance. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -127,8 +133,8 @@ cdef class ChaCha(BitGenerator): .. [1] Bernstein, D. J.. ChaCha, a variant of Salsa20. http://cr.yp.to/papers.html#chacha. 2008.01.28. """ - def __init__(self, seed=None, *, counter=None, key=None, rounds=20): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, counter=None, key=None, rounds=20, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.rng_state = PyArray_malloc_aligned( sizeof(chacha_state_t) ) diff --git a/randomgen/common.pyx b/randomgen/common.pyx index 704706eeb..50473bc50 100644 --- a/randomgen/common.pyx +++ b/randomgen/common.pyx @@ -50,7 +50,7 @@ cdef class BitGenerator(_BitGenerator): msg = ("mode is deprecated and will be removed in a future version. " "Seeding defaults to a numpy.random.SeedSequence instance.") if "numpy" in self._supported_modes(): - msg += "Use numpy_seed=True to enforce numpy-compatible seeding." + msg += " Use numpy_seed=True to enforce numpy-compatible seeding." warnings.warn(msg, FutureWarning) if mode is not _DeprecatedValue and ( not isinstance(mode, str) or mode.lower() not in self._supported_modes() diff --git a/randomgen/dsfmt.pyx b/randomgen/dsfmt.pyx index 21f84bf7f..7862856a9 100644 --- a/randomgen/dsfmt.pyx +++ b/randomgen/dsfmt.pyx @@ -3,7 +3,7 @@ import operator import numpy as np cimport numpy as np - +from randomgen._deprecated_value import _DeprecatedValue from randomgen.common cimport * __all__ = ["DSFMT"] @@ -27,7 +27,7 @@ cdef uint64_t dsfmt_raw(void *st) noexcept nogil: cdef class DSFMT(BitGenerator): """ - DSFMT(seed=None) + DSFMT(seed=None, *, mode=) Container for the SIMD-based Mersenne Twister pseudo RNG. @@ -40,6 +40,12 @@ cdef class DSFMT(BitGenerator): ``None`` (the default). If `seed` is ``None``, then 764 32-bit unsigned integers are read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -110,8 +116,8 @@ cdef class DSFMT(BitGenerator): Sequences and Their Applications - SETA, 290--298, 2008. """ - def __init__(self, seed=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.rng_state.state = PyArray_malloc_aligned(sizeof(dsfmt_t)) self.rng_state.buffered_uniforms = PyArray_calloc_aligned( DSFMT_N64, sizeof(double) diff --git a/randomgen/hc128.pyx b/randomgen/hc128.pyx index 673d7f25f..646cbe2b7 100644 --- a/randomgen/hc128.pyx +++ b/randomgen/hc128.pyx @@ -4,6 +4,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["HC128"] @@ -19,7 +20,7 @@ cdef double hc128_double(void* st) noexcept nogil: cdef class HC128(BitGenerator): """ - HC128(seed=None, *, key=None) + HC128(seed=None, *, key=None, mode=) Container for the HC-128 cipher-based pseudo-random number generator @@ -36,6 +37,12 @@ cdef class HC128(BitGenerator): Key for HC128. The key is a 256-bit integer that contains both the key (lower 128 bits) and initial values (upper 128-bits) for the HC-128 cipher. key and seed cannot both be used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -106,8 +113,8 @@ cdef class HC128(BitGenerator): .. [2] Wu, Hongjun, "Stream Ciphers HC-128 and HC-256". https://www.ntu.edu.sg/home/wuhj/research/hc/index.html) """ - def __init__(self, seed=None, *, key=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, key=None, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed, key) self._bitgen.state = &self.rng_state diff --git a/randomgen/jsf.pyx b/randomgen/jsf.pyx index e2ce5e470..5d636935a 100644 --- a/randomgen/jsf.pyx +++ b/randomgen/jsf.pyx @@ -3,7 +3,7 @@ import numpy as np cimport numpy as np - +from randomgen._deprecated_value import _DeprecatedValue from randomgen.common cimport * __all__ = ["JSF"] @@ -80,7 +80,7 @@ cdef uint64_t jsf32_raw(void* st) noexcept nogil: cdef class JSF(BitGenerator): """ - JSF(seed=None, *, seed_size=1, size=64, p=None, q=None, r=None) + JSF(seed=None, *, seed_size=1, size=64, p=None, q=None, r=None, mode=) Container for Jenkins's Fast Small (JSF) pseudo-random number generator @@ -109,6 +109,12 @@ cdef class JSF(BitGenerator): r : int, optional One the the three parameters that defines JSF. See Notes. If not provided uses the default values for the selected size listed in Notes. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -183,8 +189,8 @@ cdef class JSF(BitGenerator): parameters = JSF_PARAMETERS def __init__(self, seed=None, *, seed_size=1, size=64, p=None, q=None, - r=None): - BitGenerator.__init__(self, seed) + r=None, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) if size not in (32, 64) or not isinstance(size, INT_TYPES): raise ValueError("size must be either 32 or 64") if seed_size not in (1, 2, 3) or not isinstance(seed_size, INT_TYPES): diff --git a/randomgen/mt19937.pyx b/randomgen/mt19937.pyx index c6d03fd6d..5be8c4780 100644 --- a/randomgen/mt19937.pyx +++ b/randomgen/mt19937.pyx @@ -208,7 +208,8 @@ cdef class MT19937(BitGenerator): """ cdef MT19937 bit_generator - bit_generator = self.__class__(mode=self.mode) + kwargs = {"numpy_seed": True} if self.mode == "numpy" else {} + bit_generator = self.__class__(**kwargs) bit_generator.state = self.state mt19937_jump_default(&bit_generator.rng_state) @@ -277,7 +278,8 @@ cdef class MT19937(BitGenerator): """ cdef MT19937 bit_generator - bit_generator = self.__class__(seed=self._copy_seed(), mode=self.mode) + kwargs = {} if self.mode != "numpy" else {"numpy_seed": True} + bit_generator = self.__class__(seed=self._copy_seed(), **kwargs) bit_generator.state = self.state bit_generator.jump_inplace(jumps) diff --git a/randomgen/mt64.pyx b/randomgen/mt64.pyx index 715e5cc0a..bbb0f4182 100644 --- a/randomgen/mt64.pyx +++ b/randomgen/mt64.pyx @@ -7,6 +7,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["MT64"] @@ -24,7 +25,7 @@ cdef uint64_t mt64_raw(void *st) noexcept nogil: cdef class MT64(BitGenerator): """ - MT64(seed=None) + MT64(seed=None, *, mode=) Container for the 64-bit Mersenne Twister pseudo-random number generator @@ -38,6 +39,13 @@ cdef class MT64(BitGenerator): unsigned integers are read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. + Attributes ---------- @@ -87,8 +95,8 @@ cdef class MT64(BitGenerator): .. [2] Nishimura, T. "Tables of 64-bit Mersenne Twisters" ACM Transactions on Modeling and Computer Simulation 10. (2000) 348-357. """ - def __init__(self, seed=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed) self._bitgen.state = &self.rng_state diff --git a/randomgen/pcg32.pyx b/randomgen/pcg32.pyx index e78cbab5b..e44bb27f9 100644 --- a/randomgen/pcg32.pyx +++ b/randomgen/pcg32.pyx @@ -5,6 +5,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["PCG32"] @@ -23,7 +24,7 @@ cdef uint64_t pcg32_raw(void* st) noexcept nogil: cdef class PCG32(BitGenerator): """ - PCG32(seed=None, inc=0) + PCG32(seed=None, inc=0, *, mode=) Container for the PCG-32 pseudo-random number generator. @@ -39,6 +40,12 @@ cdef class PCG32(BitGenerator): The increment in the LCG. Can be an integer in [0, 2**64] or ``None``. The default is 0. If `inc` is ``None``, then it is initialized using entropy. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -97,8 +104,8 @@ cdef class PCG32(BitGenerator): .. [2] O'Neill, Melissa E. "PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation" """ - def __init__(self, seed=None, inc=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, inc=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed, inc) self._bitgen.state = &self.rng_state self._bitgen.next_uint64 = &pcg32_uint64 diff --git a/randomgen/pcg64.pyx b/randomgen/pcg64.pyx index f25d3565b..7824592ef 100644 --- a/randomgen/pcg64.pyx +++ b/randomgen/pcg64.pyx @@ -470,7 +470,8 @@ cdef class PCG64(BitGenerator): """ cdef PCG64 bit_generator - bit_generator = self.__class__(seed=self._copy_seed(), mode=self.mode, variant=self.variant) + kwargs = {"numpy_seed": True} if self.mode == "numpy" else {} + bit_generator = self.__class__(seed=self._copy_seed(), variant=self.variant, **kwargs) bit_generator.state = self.state bit_generator.jump_inplace(iter) diff --git a/randomgen/philox.pyx b/randomgen/philox.pyx index 81b524016..f7a8646b5 100644 --- a/randomgen/philox.pyx +++ b/randomgen/philox.pyx @@ -481,7 +481,8 @@ cdef class Philox(BitGenerator): """ cdef Philox bit_generator - bit_generator = self.__class__(seed=self._copy_seed(), mode=self.mode) + kwargs = {} if self.mode != "numpy" else {"numpy_seed": True} + bit_generator = self.__class__(seed=self._copy_seed(), **kwargs) bit_generator.state = self.state bit_generator.jump_inplace(iter) diff --git a/randomgen/sfmt.pyx b/randomgen/sfmt.pyx index 5ca65d1ca..9754261da 100644 --- a/randomgen/sfmt.pyx +++ b/randomgen/sfmt.pyx @@ -5,6 +5,7 @@ import operator import numpy as np cimport numpy as np +from randomgen._deprecated_value import _DeprecatedValue from randomgen.common cimport * @@ -29,7 +30,7 @@ cdef double sfmt_double(void* st) noexcept nogil: cdef class SFMT(BitGenerator): """ - SFMT(seed=None) + SFMT(seed=None, *, mode=) Container for the SIMD-based Mersenne Twister pseudo RNG. @@ -43,6 +44,12 @@ cdef class SFMT(BitGenerator): unsigned integers are read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -111,8 +118,8 @@ cdef class SFMT(BitGenerator): Jump Ahead Algorithm for Linear Recurrences in a Polynomial Space", Sequences and Their Applications - SETA, 290--298, 2008. """ - def __init__(self, seed=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.rng_state.state = PyArray_malloc_aligned(sizeof(sfmt_t)) self.rng_state.buffered_uint64 = PyArray_calloc_aligned(SFMT_N64, sizeof(uint64_t)) self.rng_state.buffer_loc = SFMT_N64 diff --git a/randomgen/speck128.pyx b/randomgen/speck128.pyx index 37c86b7f3..cd370d3a9 100644 --- a/randomgen/speck128.pyx +++ b/randomgen/speck128.pyx @@ -4,6 +4,7 @@ import numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["SPECK128"] @@ -21,7 +22,7 @@ cdef double speck_double(void* st) noexcept nogil: cdef class SPECK128(BitGenerator): """ - SPECK128(seed=None, *, counter=None, key=None, rounds=34) + SPECK128(seed=None, *, counter=None, key=None, rounds=34, mode=) Container for the SPECK (128 x 256) pseudo-random number generator. @@ -46,6 +47,12 @@ cdef class SPECK128(BitGenerator): Number of rounds of the SPECK algorithm to run. The default value 34 is the official value used in encryption. Reduced-round variant *might* (untested) perform well statistically with improved performance. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -133,8 +140,8 @@ cdef class SPECK128(BitGenerator): National Security Agency. January 15, 2019. from https://nsacyber.github.io/simon-speck/implementations/ImplementationGuide1.1.pdf """ - def __init__(self, seed=None, *, counter=None, key=None, rounds=SPECK_MAX_ROUNDS): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, counter=None, key=None, rounds=SPECK_MAX_ROUNDS, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) # Calloc since ctr needs to be 0 self.rng_state = PyArray_calloc_aligned(sizeof(speck_state_t), 1) if (rounds <= 0) or rounds > SPECK_MAX_ROUNDS or int(rounds) != rounds: diff --git a/randomgen/tests/test_bit_generators_against_numpy.py b/randomgen/tests/test_bit_generators_against_numpy.py index 1405d1229..4a377a1c0 100644 --- a/randomgen/tests/test_bit_generators_against_numpy.py +++ b/randomgen/tests/test_bit_generators_against_numpy.py @@ -17,14 +17,14 @@ def test_against_numpy(bg): ss = np.random.SeedSequence(1203940) np_ss = np.random.SeedSequence(1203940) kwargs = {"variant": "xsl-rr"} if bg == "PCG64" else {} - ref = bitgen(ss, mode="numpy", **kwargs) + ref = bitgen(ss, numpy_seed=True, **kwargs) exp = np_bitgen(np_ss) np.testing.assert_equal(ref.random_raw(1000), exp.random_raw(1000)) def test_pcg_numpy_mode_exception(): with pytest.raises(ValueError): - PCG64(SeedSequence(0), mode="numpy", inc=3) + PCG64(SeedSequence(0), numpy_seed=True, inc=3) @pytest.mark.parametrize("k", [1, 4]) @@ -33,7 +33,7 @@ def test_sfc_numpy_mode_exception(k, w): if k == w == 1: return with pytest.raises(ValueError): - SFC64(SeedSequence(0), mode="numpy", w=w, k=k) + SFC64(SeedSequence(0), numpy_seed=True, w=w, k=k) @pytest.mark.parametrize("number", [2, 4]) @@ -42,4 +42,4 @@ def test_philox_numpy_mode_exception(number, width): if number == 4 and width == 64: return with pytest.raises(ValueError): - Philox(SeedSequence(0), mode="numpy", number=number, width=width) + Philox(SeedSequence(0), numpy_seed=True, number=number, width=width) diff --git a/randomgen/tests/test_direct.py b/randomgen/tests/test_direct.py index d7d764e6d..b3a3a9586 100644 --- a/randomgen/tests/test_direct.py +++ b/randomgen/tests/test_direct.py @@ -38,6 +38,7 @@ Xoshiro256, Xoshiro512, ) +from randomgen._deprecated_value import _DeprecatedValue from randomgen.common import interface from randomgen.seed_sequence import ISeedSequence @@ -61,6 +62,7 @@ HAS_AESNI = aes.use_aesni USE_AESNI = [True, False] if HAS_AESNI else [False] +NO_MODE_SUPPORT = [EFIIX64, LXM, Romu, RDRAND] try: import cffi # noqa: F401 @@ -223,8 +225,14 @@ def _read_csv(cls, filename): data.append(int(line.split(",")[-1].strip(), 0)) return {"seed": seed, "data": np.array(data, dtype=cls.dtype)} - def setup_bitgenerator(self, seed, mode="sequence"): - kwargs = {} if mode == "sequence" else {"mode": mode} + def test_deprecated_mode(self): + if self.bit_generator in NO_MODE_SUPPORT or isinstance(self.bit_generator, partial): + pytest.skip("Never supported mode") + with pytest.warns(FutureWarning): + self.setup_bitgenerator([0], mode="sequence") + + def setup_bitgenerator(self, seed, mode=None): + kwargs = {} if mode is None else {"mode": mode} return self.bit_generator(*seed, **kwargs) def test_default(self): @@ -470,12 +478,7 @@ def test_uinteger_reset_jump(self): assert next_g != next_jumped def test_jumped_seed_seq_clone(self): - mode = "sequence" - try: - bg = self.setup_bitgenerator(self.data1["seed"], mode=mode) - except (TypeError, ValueError): - # Newer generators do not accept mode (TypeError) - bg = self.setup_bitgenerator(self.data1["seed"]) + bg = self.setup_bitgenerator(self.data1["seed"]) if not hasattr(bg, "jumped"): pytest.skip("bit generator does not support jumping") try: @@ -830,7 +833,7 @@ def setup_class(cls): cls.large_advance_initial = 141078743063826365544432622475512570578 cls.large_advance_final = 32639015182640331666105117402520879107 - def setup_bitgenerator(self, seed, mode="sequence", inc: int | None = None): + def setup_bitgenerator(self, seed, mode=_DeprecatedValue, inc: int | None = None): return self.bit_generator( *seed, mode=mode, variant="xsl-rr", inc=inc # type: ignore ) @@ -928,7 +931,9 @@ def test_set_key(self): def test_numpy_mode(self): ss = SeedSequence(0) - bg = self.setup_bitgenerator([ss], mode="numpy") + bg = self.setup_bitgenerator([ss], numpy_seed=True) + with pytest.warns(FutureWarning): + self.setup_bitgenerator([ss], mode="numpy") assert isinstance(bg, Philox) def test_seed_key(self): @@ -958,18 +963,14 @@ def setup_class(cls): ] def test_seed_sequence(self): - bg = self.bit_generator_base( - number=self.number, width=self.width, mode="sequence" - ) + bg = self.bit_generator_base(number=self.number, width=self.width) assert isinstance(bg, self.bit_generator_base) try: assert isinstance(bg.seed_seq, SeedSequence) except AttributeError: assert isinstance(bg._seed_seq, SeedSequence) - bg = self.bit_generator_base( - 0, number=self.number, width=self.width, mode="sequence" - ) + bg = self.bit_generator_base(0, number=self.number, width=self.width) try: assert bg.seed_seq.entropy == 0 except AttributeError: @@ -992,7 +993,7 @@ def test_seed_sequence(self): def test_numpy_mode(self): ss = SeedSequence(0) with pytest.raises(ValueError, match="n must be 4"): - self.setup_bitgenerator([ss], mode="numpy") + self.setup_bitgenerator([ss], numpy_seed=True) class TestAESCounter(TestPhilox): @@ -1407,11 +1408,8 @@ def setup_class(cls): cls.large_advance_initial = 645664597830827402 cls.large_advance_final = 3 - def setup_bitgenerator(self, seed, mode="sequence", inc=None): - kwargs = {"inc": inc} - if mode != "sequence": - kwargs["mode"] = mode - return self.bit_generator(*seed, **kwargs) + def setup_bitgenerator(self, seed, mode=_DeprecatedValue, inc=None): + return self.bit_generator(*seed, inc=inc, mode=mode) def test_advance_symmetry(self): rs = np.random.Generator(self.setup_bitgenerator(self.data1["seed"])) @@ -1841,7 +1839,8 @@ def test_invalid_rounds(self): def test_mode(): with pytest.raises(ValueError, match="mode must be one of:"): - MT19937(mode="unknown") + with pytest.warns(FutureWarning): + MT19937(mode="unknown") class TestLXM(Base): @@ -1924,16 +1923,16 @@ def setup_class(cls): def test_numpy_mode(self): with pytest.raises(ValueError, match="w and k"): - self.setup_bitgenerator([0], mode="numpy", k=3) + self.setup_bitgenerator([0], numpy_seed=True, k=3) with pytest.raises(ValueError, match="w and k"): - self.setup_bitgenerator([0], mode="numpy", w=3) + self.setup_bitgenerator([0], numpy_seed=True, w=3) class TestPCG64DXSM(Base): @classmethod def setup_class(cls): super().setup_class() - cls.bit_generator = partial(PCG64, mode="sequence", variant="dxsm-128") + cls.bit_generator = partial(PCG64, variant="dxsm-128") cls.bits = 64 cls.dtype = np.uint64 cls.data1 = cls._read_csv(join(pwd, "./data/pcg64-dxsm-testset-1.csv")) @@ -1965,7 +1964,7 @@ class TestPCG64CMDXSM(TestPCG64DXSM): @classmethod def setup_class(cls): super().setup_class() - cls.bit_generator = partial(PCG64, mode="sequence", variant="dxsm") + cls.bit_generator = partial(PCG64, variant="dxsm") cls.data1 = cls._read_csv(join(pwd, "./data/pcg64-cm-dxsm-testset-1.csv")) cls.data2 = cls._read_csv(join(pwd, "./data/pcg64-cm-dxsm-testset-2.csv")) cls.large_advance_initial = 159934576226003702342121456273047082943 diff --git a/randomgen/tests/test_extended_generator.py b/randomgen/tests/test_extended_generator.py index fc192fe0f..81d4dc62d 100644 --- a/randomgen/tests/test_extended_generator.py +++ b/randomgen/tests/test_extended_generator.py @@ -41,7 +41,7 @@ def mv_seed(): @pytest.fixture(scope="function") def extended_gen(): - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) return ExtendedGenerator(pcg) @@ -155,7 +155,7 @@ def test_multivariate_normal_method(seed, method): @pytest.mark.parametrize("method", ["svd", "eigh", "cholesky"]) def test_multivariate_normal_basic_stats(seed, method): - random = ExtendedGenerator(MT19937(seed, mode="sequence")) + random = ExtendedGenerator(MT19937(seed)) n_s = 1000 mean = np.array([1, 2]) cov = np.array([[2, 1], [1, 2]]) @@ -184,7 +184,7 @@ def test_multivariate_normal_bad_size(mean, size): def test_multivariate_normal(seed): - random = ExtendedGenerator(MT19937(seed, mode="sequence")) + random = ExtendedGenerator(MT19937(seed)) mean = (0.123456789, 10) cov = [[1, 0], [0, 1]] size = (3, 2) @@ -244,7 +244,7 @@ def test_multivariate_normal(seed): def test_complex_normal(seed): - random = ExtendedGenerator(MT19937(seed, mode="sequence")) + random = ExtendedGenerator(MT19937(seed)) actual = random.complex_normal(loc=1.0, gamma=1.0, relation=0.5, size=(3, 2)) desired = np.array( [ @@ -321,7 +321,7 @@ def test_set_get_state(seed): def test_complex_normal_size(mv_seed): - random = ExtendedGenerator(MT19937(mv_seed, mode="sequence")) + random = ExtendedGenerator(MT19937(mv_seed)) state = random.state loc = np.ones((1, 2)) gamma = np.ones((3, 1)) @@ -368,7 +368,7 @@ def test_default_pcg64(): @pytest.mark.parametrize("dim", [2, 5, 10]) @pytest.mark.parametrize("size", [None, 5, (3, 7)]) def test_standard_wishart_reproduce(df, dim, size): - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) w = eg.standard_wishart(df, dim, size) if size is not None: @@ -378,7 +378,7 @@ def test_standard_wishart_reproduce(df, dim, size): assert w.ndim == 2 assert w.shape[-2:] == (dim, dim) - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) w2 = eg.standard_wishart(df, dim, size) assert_allclose(w, w2) @@ -388,7 +388,7 @@ def test_standard_wishart_reproduce(df, dim, size): @pytest.mark.parametrize("df", [8, [10], [[5], [6]]]) def test_wishart_broadcast(df, scale_dim): dim = 5 - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) scale = np.eye(dim) for _ in range(scale_dim): @@ -402,7 +402,7 @@ def test_wishart_broadcast(df, scale_dim): assert w.shape[:-2] == np.broadcast(df, z).shape size = w.shape[:-2] - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) w2 = eg.wishart(df, scale, size=size) assert_allclose(w, w2) @@ -417,7 +417,7 @@ def test_wishart_broadcast(df, scale_dim): def test_wishart_reduced_rank(method): scale = np.eye(3) scale[0, 1] = scale[1, 0] = 1.0 - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) w = eg.wishart(10, scale, method=method, rank=2) assert w.shape == (3, 3) @@ -428,7 +428,7 @@ def test_wishart_reduced_rank(method): def test_missing_scipy_exception(): scale = np.eye(3) scale[0, 1] = scale[1, 0] = 1.0 - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) with pytest.raises(ImportError): eg.wishart(10, scale, method="cholesky", rank=2) @@ -490,7 +490,7 @@ def test_broadcast_both_paths(): def test_factor_wishart(): - pcg = PCG64(0, mode="sequence") + pcg = PCG64(0) eg = ExtendedGenerator(pcg) w = eg.wishart([3, 5], 2 * np.eye(4), size=(10000, 2), method="factor") assert_allclose(np.diag((w[:, 0] / 3).mean(0)).mean(), 4, rtol=1e-2) @@ -649,7 +649,7 @@ def test_random_other_type(extended_gen): def test_random(): - random = ExtendedGenerator(MT19937(0, mode="sequence")) + random = ExtendedGenerator(MT19937(0)) actual = random.random((3, 2)) desired = np.array( [ @@ -660,13 +660,13 @@ def test_random(): ) assert_array_almost_equal(actual, desired, decimal=15) - random = ExtendedGenerator(MT19937(0, mode="sequence")) + random = ExtendedGenerator(MT19937(0)) actual = random.random() assert_array_almost_equal(actual, desired[0, 0], decimal=15) def test_random_float(seed): - random = ExtendedGenerator(MT19937(seed, mode="sequence")) + random = ExtendedGenerator(MT19937(seed)) actual = random.random((3, 2)) desired = np.array( [[0.7165936, 0.6035045], [0.4473828, 0.359537], [0.7954794, 0.1942982]] @@ -675,7 +675,7 @@ def test_random_float(seed): def test_random_float_scalar(seed): - random = ExtendedGenerator(MT19937(seed, mode="sequence")) + random = ExtendedGenerator(MT19937(seed)) actual = random.random(dtype=np.float32) desired = 0.7165936 assert_array_almost_equal(actual, desired, decimal=7) diff --git a/randomgen/tests/test_lcg128mix_pcg64dxsm.py b/randomgen/tests/test_lcg128mix_pcg64dxsm.py index ce0e646ce..3e5b0820f 100644 --- a/randomgen/tests/test_lcg128mix_pcg64dxsm.py +++ b/randomgen/tests/test_lcg128mix_pcg64dxsm.py @@ -157,7 +157,7 @@ def test_ctypes(): def test_pcg_warnings_and_errors(): with pytest.raises(ValueError, match="variant unknown is not known"): - PCG64(0, mode="sequence", variant="unknown") + PCG64(0, variant="unknown") def test_repr(): @@ -200,7 +200,7 @@ def test_exceptions(): @pytest.mark.parametrize("seed", [0, sum([2**i for i in range(1, 128, 2)])]) @pytest.mark.parametrize("inc", [0, sum([2**i for i in range(0, 128, 3)])]) def test_equivalence_pcg64dxsm(seed, inc): - a = PCG64(seed, inc, mode="sequence", variant="dxsm") + a = PCG64(seed, inc, variant="dxsm") b = PCG64DXSM(seed, inc) assert np.all((a.random_raw(10000) - b.random_raw(10000)) == 0) assert np.all((a.random_raw(13) - b.random_raw(13)) == 0) diff --git a/randomgen/tests/test_wrapper.py b/randomgen/tests/test_wrapper.py index 6d1b3d901..39b1d34c9 100644 --- a/randomgen/tests/test_wrapper.py +++ b/randomgen/tests/test_wrapper.py @@ -48,7 +48,7 @@ def _next_32(void_p): return _next_32 -PCG64_NATIVE = PCG64(0, None, mode="sequence", variant="xsl-rr") +PCG64_NATIVE = PCG64(0, None, variant="xsl-rr") PCG64_INITIAL_STATE = PCG64_NATIVE.state @@ -62,7 +62,7 @@ def python_pcg(request): @pytest.fixture(scope="function") def pcg_native(request): - return PCG64(0, None, mode="sequence", variant="xsl-rr") + return PCG64(0, None, variant="xsl-rr") @pytest.fixture(scope="function") diff --git a/randomgen/threefry.pyx b/randomgen/threefry.pyx index 9738fec14..b9d122200 100644 --- a/randomgen/threefry.pyx +++ b/randomgen/threefry.pyx @@ -4,6 +4,7 @@ import numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["ThreeFry"] @@ -44,7 +45,7 @@ cdef uint64_t threefry2x32_raw(void *st) noexcept nogil: cdef class ThreeFry(BitGenerator): """ - ThreeFry(seed=None, *, counter=None, key=None, number=4, width=64) + ThreeFry(seed=None, *, counter=None, key=None, number=4, width=64, mode=) Container for the ThreeFry family of pseudo-random number generators. @@ -75,6 +76,12 @@ cdef class ThreeFry(BitGenerator): width : {32, 64}, optional Bit width the values produced. Maps to W in the ThreeFry variant naming scheme ThreeFryNxW. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -160,8 +167,8 @@ cdef class ThreeFry(BitGenerator): cdef int n cdef int w - def __init__(self, seed=None, *, counter=None, key=None, number=4, width=64): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, counter=None, key=None, number=4, width=64, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) if number not in (2, 4): raise ValueError("number must be either 2 or 4") if width not in (32, 64): diff --git a/randomgen/xoroshiro128.pyx b/randomgen/xoroshiro128.pyx index be4e26145..c638dfabc 100644 --- a/randomgen/xoroshiro128.pyx +++ b/randomgen/xoroshiro128.pyx @@ -5,6 +5,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["Xoroshiro128"] @@ -28,7 +29,7 @@ cdef double xoroshiro128plusplus_double(void* st) noexcept nogil: cdef class Xoroshiro128(BitGenerator): """ - Xoroshiro128(seed=None, *, plusplus=False) + Xoroshiro128(seed=None, *, plusplus=False, mode=) Container for the xoroshiro128+/++ pseudo-random number generator. @@ -44,6 +45,13 @@ cdef class Xoroshiro128(BitGenerator): plusplus : bool, default False Whether to use the ++ version (xoroshiro128++). The default is False which uses the xoroshiro128+ PRNG which + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. + Attributes ---------- @@ -123,8 +131,8 @@ cdef class Xoroshiro128(BitGenerator): .. [1] "xoroshiro+ / xorshift* / xorshift+ generators and the PRNG shootout", https://prng.di.unimi.it/ """ - def __init__(self, seed=None, *, plusplus=False): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, plusplus=False, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed) self._plusplus = plusplus self._set_generators() diff --git a/randomgen/xorshift1024.pyx b/randomgen/xorshift1024.pyx index e6e02345e..f632a95d6 100644 --- a/randomgen/xorshift1024.pyx +++ b/randomgen/xorshift1024.pyx @@ -5,7 +5,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * - +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["Xorshift1024"] @@ -20,7 +20,7 @@ cdef double xorshift1024_double(void* st) noexcept nogil: cdef class Xorshift1024(BitGenerator): """ - Xorshift1024(seed=None) + Xorshift1024(seed=None, *, mode=) Container for the xorshift1024*φ pseudo-random number generator. @@ -33,6 +33,12 @@ cdef class Xorshift1024(BitGenerator): ``None``, then data is read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -112,8 +118,8 @@ cdef class Xorshift1024(BitGenerator): .. [4] Sebastiano Vigna. "Further scramblings of Marsaglia's xorshift generators." CoRR, abs/1403.0930, 2014. """ - def __init__(self, seed=None, *): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed) self._bitgen.state = &self.rng_state diff --git a/randomgen/xoshiro256.pyx b/randomgen/xoshiro256.pyx index ecfa797ff..c78f9a34d 100644 --- a/randomgen/xoshiro256.pyx +++ b/randomgen/xoshiro256.pyx @@ -5,7 +5,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * - +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["Xoshiro256"] @@ -20,7 +20,7 @@ cdef double xoshiro256_double(void* st) noexcept nogil: cdef class Xoshiro256(BitGenerator): """ - Xoshiro256(seed=None) + Xoshiro256(seed=None, *, mode=) Container for the xoshiro256** pseudo-random number generator. @@ -33,6 +33,12 @@ cdef class Xoshiro256(BitGenerator): ``None``, then data is read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. Attributes ---------- @@ -112,8 +118,8 @@ cdef class Xoshiro256(BitGenerator): _seed_seq_len = 4 _seed_seq_dtype = np.uint64 - def __init__(self, seed=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed) self._bitgen.state = &self.rng_state diff --git a/randomgen/xoshiro512.pyx b/randomgen/xoshiro512.pyx index 06af3772d..f21d02c78 100644 --- a/randomgen/xoshiro512.pyx +++ b/randomgen/xoshiro512.pyx @@ -5,7 +5,7 @@ import numpy as np cimport numpy as np from randomgen.common cimport * - +from randomgen._deprecated_value import _DeprecatedValue __all__ = ["Xoshiro512"] @@ -20,7 +20,7 @@ cdef double xoshiro512_double(void* st) noexcept nogil: cdef class Xoshiro512(BitGenerator): """ - Xoshiro512(seed=None) + Xoshiro512(seed=None, *, mode=) Container for the xoshiro512** pseudo-random number generator. @@ -33,6 +33,13 @@ cdef class Xoshiro512(BitGenerator): is ``None``, then data is read from ``/dev/urandom`` (or the Windows analog) if available. If unavailable, a hash of the time and process ID is used. + mode : {None, "sequence"} + Deprecated parameter. Do not use. + + .. deprecated: 2.0.0 + + Starting in version 2, only seed sequences are supported. + Attributes ---------- @@ -108,8 +115,8 @@ cdef class Xoshiro512(BitGenerator): .. [1] "xoroshiro+ / xorshift* / xorshift+ generators and the PRNG shootout", https://prng.di.unimi.it/ """ - def __init__(self, seed=None): - BitGenerator.__init__(self, seed) + def __init__(self, seed=None, *, mode=_DeprecatedValue): + BitGenerator.__init__(self, seed, mode=mode) self.seed(seed) self._bitgen.state = &self.rng_state diff --git a/setup.cfg b/setup.cfg index e2debf7e4..fba969f0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,6 +62,7 @@ filterwarnings = error:`np.bool` is a deprecated alias:DeprecationWarning: error:Passing None into shape:DeprecationWarning: error:overflow encountered in scalar negative:RuntimeWarning + error:mode is deprecated and will be removed:FutureWarning [tool.ruff.lint] select = ["NPY201"] From bb9599e1ad5463c3960f3800dbc1a9cc22e7c1b2 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 23 Sep 2024 11:43:34 +0100 Subject: [PATCH 3/4] STY: Run isort on pyx files --- randomgen/_seed_sequence.pyx | 2 +- randomgen/chacha.pyx | 2 + randomgen/common.pyx | 13 +++--- randomgen/examples/cython/extending.pyx | 6 ++- .../cython/extending_distributions.pyx | 7 +++- randomgen/examples/cython/low_level.pyx | 6 ++- randomgen/generator.pyx | 42 +++++++++++++------ randomgen/mtrand.pyx | 16 +++---- randomgen/rdrand.pyx | 15 +++++-- randomgen/romu.pyx | 3 +- randomgen/sfc.pyx | 4 +- randomgen/tests/_shims.pyx | 3 +- randomgen/tests/test_direct.py | 4 +- randomgen/wrapper.pyx | 3 +- setup.cfg | 2 +- 15 files changed, 86 insertions(+), 42 deletions(-) diff --git a/randomgen/_seed_sequence.pyx b/randomgen/_seed_sequence.pyx index 4f305cac3..e5cba284e 100644 --- a/randomgen/_seed_sequence.pyx +++ b/randomgen/_seed_sequence.pyx @@ -44,8 +44,8 @@ except ImportError: randbits = SystemRandom().getrandbits import numpy as np -cimport numpy as np +cimport numpy as np from libc.stdint cimport uint32_t __all__ = ["SeedSequence", "SeedlessSeedSequence", "ISeedSequence", diff --git a/randomgen/chacha.pyx b/randomgen/chacha.pyx index ad4285a63..9afb3be83 100644 --- a/randomgen/chacha.pyx +++ b/randomgen/chacha.pyx @@ -2,7 +2,9 @@ import numpy as np from randomgen.common cimport * + from randomgen._deprecated_value import _DeprecatedValue + __all__ = ["ChaCha"] cdef uint64_t chacha_uint64(void* st) noexcept nogil: diff --git a/randomgen/common.pyx b/randomgen/common.pyx index 50473bc50..79bc3828c 100644 --- a/randomgen/common.pyx +++ b/randomgen/common.pyx @@ -1,21 +1,24 @@ #!python +from collections import namedtuple import sys import warnings -from collections import namedtuple + try: from threading import Lock except ImportError: from dummy_threading import Lock import numpy as np -from numpy.random.bit_generator cimport BitGenerator as _BitGenerator -from cpython cimport PyFloat_AsDouble + cimport numpy as np +from cpython cimport PyFloat_AsDouble +from numpy.random.bit_generator cimport BitGenerator as _BitGenerator -from randomgen.common cimport * from randomgen cimport api -from randomgen.seed_sequence import ISeedSequence +from randomgen.common cimport * + from randomgen._deprecated_value import _DeprecatedValue +from randomgen.seed_sequence import ISeedSequence ISEED_SEQUENCES = (ISeedSequence,) # NumPy 1.17 diff --git a/randomgen/examples/cython/extending.pyx b/randomgen/examples/cython/extending.pyx index 36f65f4a0..4187d76cd 100644 --- a/randomgen/examples/cython/extending.pyx +++ b/randomgen/examples/cython/extending.pyx @@ -1,12 +1,14 @@ #cython: language_level=3 +from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid from libc.stdint cimport uint32_t -from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer import numpy as np -cimport numpy as np + cimport cython +cimport numpy as np from randomgen.common cimport bitgen_t + from randomgen.xoroshiro128 import Xoroshiro128 np.import_array() diff --git a/randomgen/examples/cython/extending_distributions.pyx b/randomgen/examples/cython/extending_distributions.pyx index 0ec340110..2d1ae7014 100644 --- a/randomgen/examples/cython/extending_distributions.pyx +++ b/randomgen/examples/cython/extending_distributions.pyx @@ -1,10 +1,13 @@ #cython: language_level=3 import numpy as np -cimport numpy as np + cimport cython -from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer +cimport numpy as np +from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid + from randomgen.common cimport * from randomgen.distributions cimport random_gauss_zig + from randomgen.xoroshiro128 import Xoroshiro128 diff --git a/randomgen/examples/cython/low_level.pyx b/randomgen/examples/cython/low_level.pyx index eb98937a6..281e6fb7c 100644 --- a/randomgen/examples/cython/low_level.pyx +++ b/randomgen/examples/cython/low_level.pyx @@ -1,13 +1,15 @@ #cython: language_level=3, boundscheck=False, wraparound=False +from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid from libc.stdint cimport uint32_t -from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer import numpy as np -cimport numpy as np + cimport cython +cimport numpy as np from randomgen.common cimport bitgen_t, uint64_to_double from randomgen.xoshiro256 cimport Xoshiro256, xoshiro256_next64 + from randomgen.xoshiro256 import Xoshiro256 np.import_array() diff --git a/randomgen/generator.pyx b/randomgen/generator.pyx index a52e7d93f..66f106bb9 100644 --- a/randomgen/generator.pyx +++ b/randomgen/generator.pyx @@ -3,25 +3,41 @@ import warnings from libc.math cimport sqrt -from libc.stdint cimport ( - int64_t, - uint32_t, - uint64_t, -) +from libc.stdint cimport int64_t, uint32_t, uint64_t import numpy as np + cimport numpy as np + from randomgen.pcg64 import PCG64 -from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer -from cpython cimport (PyComplex_FromDoubles, - PyComplex_ImagAsDouble, PyComplex_RealAsDouble, - ) -from randomgen cimport api +from cpython cimport ( + PyComplex_FromDoubles, + PyComplex_ImagAsDouble, + PyComplex_RealAsDouble, +) +from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid from numpy.random cimport bitgen_t -from numpy.random.c_distributions cimport random_standard_normal, random_standard_normal_fill -from randomgen.distributions cimport random_double_fill, random_float, random_long_double_size, random_long_double_fill, random_wishart_large_df -from randomgen.common cimport double_fill, float_fill, check_output, compute_complex, validate_output_shape +from numpy.random.c_distributions cimport ( + random_standard_normal, + random_standard_normal_fill, +) + +from randomgen cimport api +from randomgen.common cimport ( + check_output, + compute_complex, + double_fill, + float_fill, + validate_output_shape, +) +from randomgen.distributions cimport ( + random_double_fill, + random_float, + random_long_double_fill, + random_long_double_size, + random_wishart_large_df, +) __all__ = ["ExtendedGenerator"] diff --git a/randomgen/mtrand.pyx b/randomgen/mtrand.pyx index 0a7edbe1f..61c9792d3 100644 --- a/randomgen/mtrand.pyx +++ b/randomgen/mtrand.pyx @@ -1,26 +1,26 @@ #!python #cython: wraparound=False, nonecheck=False, boundscheck=False, cdivision=True, language_level=3 import operator -import warnings from typing import MutableSequence +import warnings import numpy as np from randomgen.bounded_integers import _integers_types from randomgen.mt19937 import MT19937 as _MT19937 -from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer -from cpython cimport (Py_INCREF, PyFloat_AsDouble) -from libc cimport string - cimport cython +from cpython cimport Py_INCREF, PyFloat_AsDouble +from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid +from libc cimport string -from randomgen.legacy.bounded_integers cimport * +from randomgen cimport api from randomgen.common cimport * from randomgen.distributions cimport * +from randomgen.legacy.bounded_integers cimport * from randomgen.legacy.distributions cimport * -from randomgen cimport api -from typing import Callable, Any + +from typing import Any, Callable np.import_array() diff --git a/randomgen/rdrand.pyx b/randomgen/rdrand.pyx index 362d4d619..cf62541b2 100644 --- a/randomgen/rdrand.pyx +++ b/randomgen/rdrand.pyx @@ -2,14 +2,23 @@ #cython: binding=True import numpy as np + cimport numpy as np from threading import Lock -from randomgen.common cimport * -from cpython cimport PyObject -from cpython.exc cimport PyErr_SetString, PyErr_Occurred, PyErr_Clear, PyErr_Print, PyErr_Fetch, PyErr_SetObject cimport libc.stdint +from cpython cimport PyObject +from cpython.exc cimport ( + PyErr_Clear, + PyErr_Fetch, + PyErr_Occurred, + PyErr_Print, + PyErr_SetObject, + PyErr_SetString, +) + +from randomgen.common cimport * np.import_array() diff --git a/randomgen/romu.pyx b/randomgen/romu.pyx index 0b9eb4571..07a37516a 100644 --- a/randomgen/romu.pyx +++ b/randomgen/romu.pyx @@ -4,8 +4,9 @@ import warnings import numpy as np -cimport numpy as np + cimport cython +cimport numpy as np from randomgen.common cimport * diff --git a/randomgen/sfc.pyx b/randomgen/sfc.pyx index 9b556cb34..2994fa21a 100644 --- a/randomgen/sfc.pyx +++ b/randomgen/sfc.pyx @@ -4,10 +4,12 @@ import warnings import numpy as np -cimport numpy as np + cimport cython +cimport numpy as np from randomgen.common cimport * + from randomgen._deprecated_value import _DeprecatedValue __all__ = ["SFC64"] diff --git a/randomgen/tests/_shims.pyx b/randomgen/tests/_shims.pyx index 6fb1df294..70d29bea9 100644 --- a/randomgen/tests/_shims.pyx +++ b/randomgen/tests/_shims.pyx @@ -1,4 +1,5 @@ -from randomgen.common cimport view_little_endian, int_to_array, byteswap_little_endian +from randomgen.common cimport byteswap_little_endian, int_to_array, view_little_endian + import numpy as np diff --git a/randomgen/tests/test_direct.py b/randomgen/tests/test_direct.py index b3a3a9586..772bde535 100644 --- a/randomgen/tests/test_direct.py +++ b/randomgen/tests/test_direct.py @@ -226,7 +226,9 @@ def _read_csv(cls, filename): return {"seed": seed, "data": np.array(data, dtype=cls.dtype)} def test_deprecated_mode(self): - if self.bit_generator in NO_MODE_SUPPORT or isinstance(self.bit_generator, partial): + if self.bit_generator in NO_MODE_SUPPORT or isinstance( + self.bit_generator, partial + ): pytest.skip("Never supported mode") with pytest.warns(FutureWarning): self.setup_bitgenerator([0], mode="sequence") diff --git a/randomgen/wrapper.pyx b/randomgen/wrapper.pyx index 04409fb58..69366058c 100644 --- a/randomgen/wrapper.pyx +++ b/randomgen/wrapper.pyx @@ -2,7 +2,8 @@ #cython: binding=True from randomgen.common cimport * -from randomgen.distributions cimport next_uint64_t, next_uint32_t, next_double_t +from randomgen.distributions cimport next_double_t, next_uint32_t, next_uint64_t + import ctypes __all__ = ["UserBitGenerator"] diff --git a/setup.cfg b/setup.cfg index fba969f0b..3c8faa5be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ sections=FUTURE,COMPAT,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER known_first_party=randomgen known_third_party=Cython,numpy,matplotlib,pandas,patsy,pytest,statsmodels,seaborn combine_as_imports=True -skip_glob=**/**/*.pyx,**/**/*.in +skip_glob=**/**/*.in known_compat=setuptools,setuptools.* force_sort_within_sections=True force_to_top=True From a4f25090bc1ff539b4c9d9df3287e3310537a110 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Mon, 23 Sep 2024 11:52:26 +0100 Subject: [PATCH 4/4] CI: Improve pep8speaks --- .pep8speaks.yml | 7 ++++++- setup.cfg | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.pep8speaks.yml b/.pep8speaks.yml index 928ba6cef..00826f2d0 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -1,11 +1,16 @@ scanner: diff_only: False - linter: flake8 # Other option is flake8 + linter: pycodestyle # Other option is flake8 pycodestyle: # Same as scanner.linter value. Other option is flake8 max-line-length: 99 # Default is 79 in PEP 8 ignore: # Errors and warnings to ignore - E203 # Whitespace before ':' - W503 # Line break occurred before a binary operator (W503) + - E301 + - E302 + - E305 + - E501 + - E701 no_blank_comment: False # If True, no comment is made on PR without any errors. diff --git a/setup.cfg b/setup.cfg index 3c8faa5be..7fa61291c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,11 @@ description_file = README.md license_files = LICENSE.md +[pycodestyle] +max-line-length = 88 +statistics = True +ignore = E203, E301, E302, E305, E501, E701, W503 + [flake8] max-line-length = 88 extend-ignore = E203, E301, E302, E305