From d7c659f6349b0b41df637bbd3b52c4e04b2963e4 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Wed, 5 Mar 2025 19:20:13 +0100 Subject: [PATCH 01/14] #32-update to Python 3.12 Started right now. --- Python3.12/PyRandLib/__init__.py | 44 +++ Python3.12/PyRandLib/basecwg.py | 113 ++++++++ Python3.12/PyRandLib/baselcg.py | 105 +++++++ Python3.12/PyRandLib/baselfib64.py | 206 +++++++++++++ Python3.12/PyRandLib/basemelg.py | 200 +++++++++++++ Python3.12/PyRandLib/basemrg.py | 182 ++++++++++++ Python3.12/PyRandLib/basepcg.py | 118 ++++++++ Python3.12/PyRandLib/baserandom.py | 397 ++++++++++++++++++++++++++ Python3.12/PyRandLib/basesquares.py | 161 +++++++++++ Python3.12/PyRandLib/basewell.py | 289 +++++++++++++++++++ Python3.12/PyRandLib/basexoroshiro.py | 189 ++++++++++++ Python3.12/PyRandLib/cwg128.py | 160 +++++++++++ Python3.12/PyRandLib/cwg128_64.py | 153 ++++++++++ Python3.12/PyRandLib/cwg64.py | 153 ++++++++++ Python3.12/PyRandLib/fastrand32.py | 121 ++++++++ Python3.12/PyRandLib/fastrand63.py | 133 +++++++++ Python3.12/PyRandLib/lfib116.py | 134 +++++++++ Python3.12/PyRandLib/lfib1340.py | 136 +++++++++ Python3.12/PyRandLib/lfib668.py | 135 +++++++++ Python3.12/PyRandLib/lfib78.py | 134 +++++++++ Python3.12/PyRandLib/melg19937.py | 120 ++++++++ Python3.12/PyRandLib/melg44497.py | 119 ++++++++ Python3.12/PyRandLib/melg607.py | 119 ++++++++ Python3.12/PyRandLib/mrg1457.py | 149 ++++++++++ Python3.12/PyRandLib/mrg287.py | 150 ++++++++++ Python3.12/PyRandLib/mrg49507.py | 140 +++++++++ Python3.12/PyRandLib/pcg1024_32.py | 281 ++++++++++++++++++ Python3.12/PyRandLib/pcg128_64.py | 175 ++++++++++++ Python3.12/PyRandLib/pcg64_32.py | 154 ++++++++++ Python3.12/PyRandLib/splitmix.py | 141 +++++++++ Python3.12/PyRandLib/squares32.py | 111 +++++++ Python3.12/PyRandLib/squares64.py | 133 +++++++++ Python3.12/PyRandLib/well1024a.py | 138 +++++++++ Python3.12/PyRandLib/well19937c.py | 140 +++++++++ Python3.12/PyRandLib/well44497b.py | 140 +++++++++ Python3.12/PyRandLib/well512a.py | 139 +++++++++ Python3.12/PyRandLib/xoroshiro1024.py | 191 +++++++++++++ Python3.12/PyRandLib/xoroshiro256.py | 180 ++++++++++++ Python3.12/PyRandLib/xoroshiro512.py | 182 ++++++++++++ Python3.12/testCPUPerfs.py | 81 ++++++ Python3.12/testED.py | 142 +++++++++ 41 files changed, 6388 insertions(+) create mode 100644 Python3.12/PyRandLib/__init__.py create mode 100644 Python3.12/PyRandLib/basecwg.py create mode 100644 Python3.12/PyRandLib/baselcg.py create mode 100644 Python3.12/PyRandLib/baselfib64.py create mode 100644 Python3.12/PyRandLib/basemelg.py create mode 100644 Python3.12/PyRandLib/basemrg.py create mode 100644 Python3.12/PyRandLib/basepcg.py create mode 100644 Python3.12/PyRandLib/baserandom.py create mode 100644 Python3.12/PyRandLib/basesquares.py create mode 100644 Python3.12/PyRandLib/basewell.py create mode 100644 Python3.12/PyRandLib/basexoroshiro.py create mode 100644 Python3.12/PyRandLib/cwg128.py create mode 100644 Python3.12/PyRandLib/cwg128_64.py create mode 100644 Python3.12/PyRandLib/cwg64.py create mode 100644 Python3.12/PyRandLib/fastrand32.py create mode 100644 Python3.12/PyRandLib/fastrand63.py create mode 100644 Python3.12/PyRandLib/lfib116.py create mode 100644 Python3.12/PyRandLib/lfib1340.py create mode 100644 Python3.12/PyRandLib/lfib668.py create mode 100644 Python3.12/PyRandLib/lfib78.py create mode 100644 Python3.12/PyRandLib/melg19937.py create mode 100644 Python3.12/PyRandLib/melg44497.py create mode 100644 Python3.12/PyRandLib/melg607.py create mode 100644 Python3.12/PyRandLib/mrg1457.py create mode 100644 Python3.12/PyRandLib/mrg287.py create mode 100644 Python3.12/PyRandLib/mrg49507.py create mode 100644 Python3.12/PyRandLib/pcg1024_32.py create mode 100644 Python3.12/PyRandLib/pcg128_64.py create mode 100644 Python3.12/PyRandLib/pcg64_32.py create mode 100644 Python3.12/PyRandLib/splitmix.py create mode 100644 Python3.12/PyRandLib/squares32.py create mode 100644 Python3.12/PyRandLib/squares64.py create mode 100644 Python3.12/PyRandLib/well1024a.py create mode 100644 Python3.12/PyRandLib/well19937c.py create mode 100644 Python3.12/PyRandLib/well44497b.py create mode 100644 Python3.12/PyRandLib/well512a.py create mode 100644 Python3.12/PyRandLib/xoroshiro1024.py create mode 100644 Python3.12/PyRandLib/xoroshiro256.py create mode 100644 Python3.12/PyRandLib/xoroshiro512.py create mode 100644 Python3.12/testCPUPerfs.py create mode 100644 Python3.12/testED.py diff --git a/Python3.12/PyRandLib/__init__.py b/Python3.12/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.12/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/Python3.12/PyRandLib/basecwg.py b/Python3.12/PyRandLib/basecwg.py new file mode 100644 index 0000000..d8b51bf --- /dev/null +++ b/Python3.12/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.12/PyRandLib/baselcg.py b/Python3.12/PyRandLib/baselcg.py new file mode 100644 index 0000000..455c230 --- /dev/null +++ b/Python3.12/PyRandLib/baselcg.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.12/PyRandLib/baselfib64.py b/Python3.12/PyRandLib/baselfib64.py new file mode 100644 index 0000000..d1ef6c7 --- /dev/null +++ b/Python3.12/PyRandLib/baselfib64.py @@ -0,0 +1,206 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.12/PyRandLib/basemelg.py b/Python3.12/PyRandLib/basemelg.py new file mode 100644 index 0000000..5b3faf2 --- /dev/null +++ b/Python3.12/PyRandLib/basemelg.py @@ -0,0 +1,200 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.12/PyRandLib/basemrg.py b/Python3.12/PyRandLib/basemrg.py new file mode 100644 index 0000000..5e05b15 --- /dev/null +++ b/Python3.12/PyRandLib/basemrg.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.12/PyRandLib/basepcg.py b/Python3.12/PyRandLib/basepcg.py new file mode 100644 index 0000000..066e7bb --- /dev/null +++ b/Python3.12/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.12/PyRandLib/baserandom.py b/Python3.12/PyRandLib/baserandom.py new file mode 100644 index 0000000..469fe65 --- /dev/null +++ b/Python3.12/PyRandLib/baserandom.py @@ -0,0 +1,397 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | expovariate(self, lambd) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int, /) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int, /) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def seed(self, _seed: SeedStateType = None, /) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Numerical | tuple[Numerical] | list[Numerical] = 1.0, + /, + times: int = 1 ) -> Numerical | list[Numerical]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64, /) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + hiMask = ((1 << _bitsCount) - 1) ^ (loMask := (1 << (_bitsCount - _rotCount)) - 1) + return ((_value & loMask) << _rotCount) | ((_value & hiMask) >> (_bitsCount - _rotCount)) + + +#===== end of module baserandom.py ===================================== diff --git a/Python3.12/PyRandLib/basesquares.py b/Python3.12/PyRandLib/basesquares.py new file mode 100644 index 0000000..47017c6 --- /dev/null +++ b/Python3.12/PyRandLib/basesquares.py @@ -0,0 +1,161 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.12/PyRandLib/basewell.py b/Python3.12/PyRandLib/basewell.py new file mode 100644 index 0000000..53e052c --- /dev/null +++ b/Python3.12/PyRandLib/basewell.py @@ -0,0 +1,289 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.12/PyRandLib/basexoroshiro.py b/Python3.12/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..81a7514 --- /dev/null +++ b/Python3.12/PyRandLib/basexoroshiro.py @@ -0,0 +1,189 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.12/PyRandLib/cwg128.py b/Python3.12/PyRandLib/cwg128.py new file mode 100644 index 0000000..f5350bc --- /dev/null +++ b/Python3.12/PyRandLib/cwg128.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.12/PyRandLib/cwg128_64.py b/Python3.12/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..67ff0e2 --- /dev/null +++ b/Python3.12/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.12/PyRandLib/cwg64.py b/Python3.12/PyRandLib/cwg64.py new file mode 100644 index 0000000..1ed40b8 --- /dev/null +++ b/Python3.12/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int | float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.12/PyRandLib/fastrand32.py b/Python3.12/PyRandLib/fastrand32.py new file mode 100644 index 0000000..26a25c1 --- /dev/null +++ b/Python3.12/PyRandLib/fastrand32.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.12/PyRandLib/fastrand63.py b/Python3.12/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4e10144 --- /dev/null +++ b/Python3.12/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.12/PyRandLib/lfib116.py b/Python3.12/PyRandLib/lfib116.py new file mode 100644 index 0000000..5083c07 --- /dev/null +++ b/Python3.12/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.12/PyRandLib/lfib1340.py b/Python3.12/PyRandLib/lfib1340.py new file mode 100644 index 0000000..0f4ecdf --- /dev/null +++ b/Python3.12/PyRandLib/lfib1340.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.12/PyRandLib/lfib668.py b/Python3.12/PyRandLib/lfib668.py new file mode 100644 index 0000000..3ae6989 --- /dev/null +++ b/Python3.12/PyRandLib/lfib668.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.12/PyRandLib/lfib78.py b/Python3.12/PyRandLib/lfib78.py new file mode 100644 index 0000000..f2cdeda --- /dev/null +++ b/Python3.12/PyRandLib/lfib78.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.12/PyRandLib/melg19937.py b/Python3.12/PyRandLib/melg19937.py new file mode 100644 index 0000000..10a3332 --- /dev/null +++ b/Python3.12/PyRandLib/melg19937.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.12/PyRandLib/melg44497.py b/Python3.12/PyRandLib/melg44497.py new file mode 100644 index 0000000..5f14081 --- /dev/null +++ b/Python3.12/PyRandLib/melg44497.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.12/PyRandLib/melg607.py b/Python3.12/PyRandLib/melg607.py new file mode 100644 index 0000000..d6c10b6 --- /dev/null +++ b/Python3.12/PyRandLib/melg607.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.12/PyRandLib/mrg1457.py b/Python3.12/PyRandLib/mrg1457.py new file mode 100644 index 0000000..007b8fd --- /dev/null +++ b/Python3.12/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.12/PyRandLib/mrg287.py b/Python3.12/PyRandLib/mrg287.py new file mode 100644 index 0000000..093406d --- /dev/null +++ b/Python3.12/PyRandLib/mrg287.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.12/PyRandLib/mrg49507.py b/Python3.12/PyRandLib/mrg49507.py new file mode 100644 index 0000000..a196c65 --- /dev/null +++ b/Python3.12/PyRandLib/mrg49507.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.12/PyRandLib/pcg1024_32.py b/Python3.12/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..586b6ad --- /dev/null +++ b/Python3.12/PyRandLib/pcg1024_32.py @@ -0,0 +1,281 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState ) + + case 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + case _: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.12/PyRandLib/pcg128_64.py b/Python3.12/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..ddef70e --- /dev/null +++ b/Python3.12/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.12/PyRandLib/pcg64_32.py b/Python3.12/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..4eed457 --- /dev/null +++ b/Python3.12/PyRandLib/pcg64_32.py @@ -0,0 +1,154 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.12/PyRandLib/splitmix.py b/Python3.12/PyRandLib/splitmix.py new file mode 100644 index 0000000..0b97a51 --- /dev/null +++ b/Python3.12/PyRandLib/splitmix.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.12/PyRandLib/squares32.py b/Python3.12/PyRandLib/squares32.py new file mode 100644 index 0000000..268b56e --- /dev/null +++ b/Python3.12/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.12/PyRandLib/squares64.py b/Python3.12/PyRandLib/squares64.py new file mode 100644 index 0000000..5efd5b5 --- /dev/null +++ b/Python3.12/PyRandLib/squares64.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.12/PyRandLib/well1024a.py b/Python3.12/PyRandLib/well1024a.py new file mode 100644 index 0000000..dfd7085 --- /dev/null +++ b/Python3.12/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.12/PyRandLib/well19937c.py b/Python3.12/PyRandLib/well19937c.py new file mode 100644 index 0000000..75ccee7 --- /dev/null +++ b/Python3.12/PyRandLib/well19937c.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 623, 622 + + case 1: + i_1, i_2 = 0, 623 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.12/PyRandLib/well44497b.py b/Python3.12/PyRandLib/well44497b.py new file mode 100644 index 0000000..9a14931 --- /dev/null +++ b/Python3.12/PyRandLib/well44497b.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 1390, 1389 + + case 1: + i_1, i_2 = 0, 1390 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.12/PyRandLib/well512a.py b/Python3.12/PyRandLib/well512a.py new file mode 100644 index 0000000..aaf532e --- /dev/null +++ b/Python3.12/PyRandLib/well512a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.12/PyRandLib/xoroshiro1024.py b/Python3.12/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..9a0f7a1 --- /dev/null +++ b/Python3.12/PyRandLib/xoroshiro1024.py @@ -0,0 +1,191 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.12/PyRandLib/xoroshiro256.py b/Python3.12/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..631b301 --- /dev/null +++ b/Python3.12/PyRandLib/xoroshiro256.py @@ -0,0 +1,180 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.12/PyRandLib/xoroshiro512.py b/Python3.12/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..10d3c0a --- /dev/null +++ b/Python3.12/PyRandLib/xoroshiro512.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.12/testCPUPerfs.py b/Python3.12/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.12/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.12/testED.py b/Python3.12/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.12/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= From 6b6242addcef0a6e03fcf683e8eb766e968df5a3 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Wed, 5 Mar 2025 19:20:44 +0100 Subject: [PATCH 02/14] #133-use new keyword type for Py 3.12 Completed. --- Python3.12/PyRandLib/annotation_types.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Python3.12/PyRandLib/annotation_types.py diff --git a/Python3.12/PyRandLib/annotation_types.py b/Python3.12/PyRandLib/annotation_types.py new file mode 100644 index 0000000..cd7b252 --- /dev/null +++ b/Python3.12/PyRandLib/annotation_types.py @@ -0,0 +1,33 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +type Numerical = int | float +type StatesList = tuple[int] | list[int] +type StatesListAndExt = tuple[StatesList, int] +type StateType = StatesList | StatesListAndExt +type SeedStateType = Numerical | StateType + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) From fa18a00a4ed36be82f1f9963a2e84f687ad91d7e Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Wed, 5 Mar 2025 19:58:27 +0100 Subject: [PATCH 03/14] #134-use new decorator override in Py3.12 Completed. Validated. --- Python3.12/PyRandLib/basecwg.py | 3 +++ Python3.12/PyRandLib/baselcg.py | 3 +++ Python3.12/PyRandLib/baselfib64.py | 4 +++- Python3.12/PyRandLib/basemelg.py | 4 +++- Python3.12/PyRandLib/basemrg.py | 4 ++++ Python3.12/PyRandLib/basepcg.py | 3 +++ Python3.12/PyRandLib/baserandom.py | 7 +++++++ Python3.12/PyRandLib/basesquares.py | 4 ++++ Python3.12/PyRandLib/basewell.py | 4 +++- Python3.12/PyRandLib/basexoroshiro.py | 4 +++- Python3.12/PyRandLib/cwg128.py | 4 +++- Python3.12/PyRandLib/cwg128_64.py | 4 +++- Python3.12/PyRandLib/cwg64.py | 4 +++- Python3.12/PyRandLib/fastrand32.py | 4 ++++ Python3.12/PyRandLib/fastrand63.py | 4 +++- Python3.12/PyRandLib/lfib116.py | 3 ++- Python3.12/PyRandLib/lfib1340.py | 3 ++- Python3.12/PyRandLib/lfib668.py | 3 ++- Python3.12/PyRandLib/lfib78.py | 3 ++- Python3.12/PyRandLib/melg19937.py | 3 ++- Python3.12/PyRandLib/melg44497.py | 3 ++- Python3.12/PyRandLib/melg607.py | 3 ++- Python3.12/PyRandLib/mrg1457.py | 3 ++- Python3.12/PyRandLib/mrg287.py | 3 ++- Python3.12/PyRandLib/mrg49507.py | 3 ++- Python3.12/PyRandLib/pcg1024_32.py | 5 ++++- Python3.12/PyRandLib/pcg128_64.py | 4 +++- Python3.12/PyRandLib/pcg64_32.py | 3 +++ Python3.12/PyRandLib/splitmix.py | 3 +++ Python3.12/PyRandLib/squares32.py | 3 +++ Python3.12/PyRandLib/squares64.py | 3 +++ Python3.12/PyRandLib/well1024a.py | 3 ++- Python3.12/PyRandLib/well19937c.py | 3 ++- Python3.12/PyRandLib/well44497b.py | 3 ++- Python3.12/PyRandLib/well512a.py | 3 ++- Python3.12/PyRandLib/xoroshiro1024.py | 6 +++++- Python3.12/PyRandLib/xoroshiro256.py | 6 ++++++ Python3.12/PyRandLib/xoroshiro512.py | 6 +++++- 38 files changed, 115 insertions(+), 26 deletions(-) diff --git a/Python3.12/PyRandLib/basecwg.py b/Python3.12/PyRandLib/basecwg.py index d8b51bf..c437dd8 100644 --- a/Python3.12/PyRandLib/basecwg.py +++ b/Python3.12/PyRandLib/basecwg.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baserandom import BaseRandom from .annotation_types import SeedStateType, StatesListAndExt @@ -96,6 +98,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> StatesListAndExt: """Returns an object capturing the current internal state of the generator. diff --git a/Python3.12/PyRandLib/baselcg.py b/Python3.12/PyRandLib/baselcg.py index 455c230..91e2d78 100644 --- a/Python3.12/PyRandLib/baselcg.py +++ b/Python3.12/PyRandLib/baselcg.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baserandom import BaseRandom from .annotation_types import Numerical @@ -91,6 +93,7 @@ def __init__(self, _seedState: Numerical = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> int: """Returns an object capturing the current internal state of the generator. diff --git a/Python3.12/PyRandLib/baselfib64.py b/Python3.12/PyRandLib/baselfib64.py index d1ef6c7..25ed4c8 100644 --- a/Python3.12/PyRandLib/baselfib64.py +++ b/Python3.12/PyRandLib/baselfib64.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baserandom import BaseRandom from .annotation_types import Numerical, SeedStateType, StateType @@ -131,6 +131,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. @@ -142,6 +143,7 @@ def getstate(self) -> StateType: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: StateType, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/basemelg.py b/Python3.12/PyRandLib/basemelg.py index 5b3faf2..94ce4da 100644 --- a/Python3.12/PyRandLib/basemelg.py +++ b/Python3.12/PyRandLib/basemelg.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baserandom import BaseRandom from .annotation_types import SeedStateType, StateType @@ -122,6 +122,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. @@ -133,6 +134,7 @@ def getstate(self) -> StateType: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: StateType, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/basemrg.py b/Python3.12/PyRandLib/basemrg.py index 5e05b15..c8e4ef3 100644 --- a/Python3.12/PyRandLib/basemrg.py +++ b/Python3.12/PyRandLib/basemrg.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baserandom import BaseRandom from .annotation_types import Numerical, SeedStateType, StateType from .splitmix import SplitMix64 @@ -107,6 +109,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. @@ -118,6 +121,7 @@ def getstate(self) -> StateType: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: StateType, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/basepcg.py b/Python3.12/PyRandLib/basepcg.py index 066e7bb..1d8f17e 100644 --- a/Python3.12/PyRandLib/basepcg.py +++ b/Python3.12/PyRandLib/basepcg.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baserandom import BaseRandom from .annotation_types import Numerical @@ -104,6 +106,7 @@ def __init__(self, _seedState: Numerical = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> int: """Returns an object capturing the current internal state of the generator. diff --git a/Python3.12/PyRandLib/baserandom.py b/Python3.12/PyRandLib/baserandom.py index 469fe65..5fa8a7c 100644 --- a/Python3.12/PyRandLib/baserandom.py +++ b/Python3.12/PyRandLib/baserandom.py @@ -22,6 +22,7 @@ #============================================================================= from random import Random +from typing import override from .annotation_types import Numerical, SeedStateType, StateType @@ -281,6 +282,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def random(self) -> float: """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). @@ -292,6 +294,7 @@ def random(self) -> float: #------------------------------------------------------------------------- + @override def getrandbits(self, k: int, /) -> int: """Returns k bits from the internal state of the generator. @@ -304,6 +307,7 @@ def getrandbits(self, k: int, /) -> int: #------------------------------------------------------------------------- + @override def randbytes(self, n: int, /) -> bytes: """Generates n random bytes. @@ -315,6 +319,7 @@ def randbytes(self, n: int, /) -> bytes: #------------------------------------------------------------------------- + @override def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. @@ -325,6 +330,7 @@ def getstate(self) -> StateType: #------------------------------------------------------------------------- + @override def setstate(self, _state: StateType, /) -> None: """Restores the internal state of the generator. @@ -337,6 +343,7 @@ def setstate(self, _state: StateType, /) -> None: #------------------------------------------------------------------------- + @override def seed(self, _seed: SeedStateType = None, /) -> None: """Initiates the internal state of this pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/basesquares.py b/Python3.12/PyRandLib/basesquares.py index 47017c6..0771c01 100644 --- a/Python3.12/PyRandLib/basesquares.py +++ b/Python3.12/PyRandLib/basesquares.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baserandom import BaseRandom from .annotation_types import SeedStateType, StatesList from .splitmix import SplitMix32 @@ -91,6 +93,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> StatesList: """Returns an object capturing the current internal state of the generator. """ @@ -98,6 +101,7 @@ def getstate(self) -> StatesList: #------------------------------------------------------------------------- + @override def setstate(self, _state: SeedStateType, /) -> None: """Restores or sets the internal state of the generator. """ diff --git a/Python3.12/PyRandLib/basewell.py b/Python3.12/PyRandLib/basewell.py index 53e052c..a89085e 100644 --- a/Python3.12/PyRandLib/basewell.py +++ b/Python3.12/PyRandLib/basewell.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baserandom import BaseRandom from .annotation_types import SeedStateType, StateType @@ -123,6 +123,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. @@ -134,6 +135,7 @@ def getstate(self) -> StateType: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: StateType, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/basexoroshiro.py b/Python3.12/PyRandLib/basexoroshiro.py index 81a7514..a84d442 100644 --- a/Python3.12/PyRandLib/basexoroshiro.py +++ b/Python3.12/PyRandLib/basexoroshiro.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baserandom import BaseRandom from .annotation_types import Numerical, StatesList, StateType @@ -131,6 +131,7 @@ def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: #------------------------------------------------------------------------- + @override def getstate(self) -> list[int]: """Returns an object capturing the current internal state of the generator. @@ -141,6 +142,7 @@ def getstate(self) -> list[int]: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/cwg128.py b/Python3.12/PyRandLib/cwg128.py index f5350bc..080c3d3 100644 --- a/Python3.12/PyRandLib/cwg128.py +++ b/Python3.12/PyRandLib/cwg128.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basecwg import BaseCWG from .annotation_types import SeedStateType @@ -118,6 +118,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -130,6 +131,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def setstate(self, _state: SeedStateType = None, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/cwg128_64.py b/Python3.12/PyRandLib/cwg128_64.py index 67ff0e2..a4af759 100644 --- a/Python3.12/PyRandLib/cwg128_64.py +++ b/Python3.12/PyRandLib/cwg128_64.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basecwg import BaseCWG from .annotation_types import SeedStateType @@ -111,6 +111,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -123,6 +124,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def setstate(self, _state: SeedStateType = None, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/cwg64.py b/Python3.12/PyRandLib/cwg64.py index 1ed40b8..3d2fb67 100644 --- a/Python3.12/PyRandLib/cwg64.py +++ b/Python3.12/PyRandLib/cwg64.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basecwg import BaseCWG from .annotation_types import SeedStateType @@ -111,6 +111,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -123,6 +124,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def setstate(self, _state: SeedStateType = None, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/fastrand32.py b/Python3.12/PyRandLib/fastrand32.py index 26a25c1..5a4bfac 100644 --- a/Python3.12/PyRandLib/fastrand32.py +++ b/Python3.12/PyRandLib/fastrand32.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baselcg import BaseLCG from .annotation_types import Numerical from .splitmix import SplitMix32 @@ -96,6 +98,7 @@ def __init__(self, _seed: Numerical = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -104,6 +107,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def setstate(self, _state: Numerical, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/fastrand63.py b/Python3.12/PyRandLib/fastrand63.py index 4e10144..60e7264 100644 --- a/Python3.12/PyRandLib/fastrand63.py +++ b/Python3.12/PyRandLib/fastrand63.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baselcg import BaseLCG from .annotation_types import Numerical @@ -109,6 +109,7 @@ class FastRand63( BaseLCG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -116,6 +117,7 @@ def next(self) -> int: return self._state #------------------------------------------------------------------------- + @override def setstate(self, _state: Numerical, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/lfib116.py b/Python3.12/PyRandLib/lfib116.py index 5083c07..238a277 100644 --- a/Python3.12/PyRandLib/lfib116.py +++ b/Python3.12/PyRandLib/lfib116.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baselfib64 import BaseLFib64 @@ -115,6 +115,7 @@ class LFib116( BaseLFib64 ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/lfib1340.py b/Python3.12/PyRandLib/lfib1340.py index 0f4ecdf..9458baf 100644 --- a/Python3.12/PyRandLib/lfib1340.py +++ b/Python3.12/PyRandLib/lfib1340.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baselfib64 import BaseLFib64 @@ -116,6 +116,7 @@ class LFib1340( BaseLFib64 ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/lfib668.py b/Python3.12/PyRandLib/lfib668.py index 3ae6989..383c24a 100644 --- a/Python3.12/PyRandLib/lfib668.py +++ b/Python3.12/PyRandLib/lfib668.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baselfib64 import BaseLFib64 @@ -115,6 +115,7 @@ class LFib668( BaseLFib64 ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/lfib78.py b/Python3.12/PyRandLib/lfib78.py index f2cdeda..f505452 100644 --- a/Python3.12/PyRandLib/lfib78.py +++ b/Python3.12/PyRandLib/lfib78.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baselfib64 import BaseLFib64 @@ -114,6 +114,7 @@ class LFib78( BaseLFib64 ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/melg19937.py b/Python3.12/PyRandLib/melg19937.py index 10a3332..e061cae 100644 --- a/Python3.12/PyRandLib/melg19937.py +++ b/Python3.12/PyRandLib/melg19937.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basemelg import BaseMELG @@ -101,6 +101,7 @@ class Melg19937( BaseMELG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. diff --git a/Python3.12/PyRandLib/melg44497.py b/Python3.12/PyRandLib/melg44497.py index 5f14081..977f06e 100644 --- a/Python3.12/PyRandLib/melg44497.py +++ b/Python3.12/PyRandLib/melg44497.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basemelg import BaseMELG @@ -100,6 +100,7 @@ class Melg44497( BaseMELG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. diff --git a/Python3.12/PyRandLib/melg607.py b/Python3.12/PyRandLib/melg607.py index d6c10b6..a34f222 100644 --- a/Python3.12/PyRandLib/melg607.py +++ b/Python3.12/PyRandLib/melg607.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basemelg import BaseMELG @@ -100,6 +100,7 @@ class Melg607( BaseMELG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. diff --git a/Python3.12/PyRandLib/mrg1457.py b/Python3.12/PyRandLib/mrg1457.py index 007b8fd..ab4c64c 100644 --- a/Python3.12/PyRandLib/mrg1457.py +++ b/Python3.12/PyRandLib/mrg1457.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basemrg import BaseMRG @@ -122,6 +122,7 @@ class Mrg1457( BaseMRG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/mrg287.py b/Python3.12/PyRandLib/mrg287.py index 093406d..0f19953 100644 --- a/Python3.12/PyRandLib/mrg287.py +++ b/Python3.12/PyRandLib/mrg287.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basemrg import BaseMRG @@ -121,6 +121,7 @@ class Mrg287( BaseMRG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/mrg49507.py b/Python3.12/PyRandLib/mrg49507.py index a196c65..dc0cda6 100644 --- a/Python3.12/PyRandLib/mrg49507.py +++ b/Python3.12/PyRandLib/mrg49507.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basemrg import BaseMRG @@ -120,6 +120,7 @@ class Mrg49507( BaseMRG ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/pcg1024_32.py b/Python3.12/PyRandLib/pcg1024_32.py index 586b6ad..89dd52b 100644 --- a/Python3.12/PyRandLib/pcg1024_32.py +++ b/Python3.12/PyRandLib/pcg1024_32.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .annotation_types import Numerical, SeedStateType, StateType from .pcg64_32 import Pcg64_32 @@ -128,6 +128,7 @@ def __init__(self, _seed: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -141,6 +142,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. @@ -151,6 +153,7 @@ def getstate(self) -> StateType: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: SeedStateType, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/pcg128_64.py b/Python3.12/PyRandLib/pcg128_64.py index ddef70e..3507087 100644 --- a/Python3.12/PyRandLib/pcg128_64.py +++ b/Python3.12/PyRandLib/pcg128_64.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basepcg import BasePCG from .annotation_types import Numerical @@ -133,6 +133,7 @@ def __init__(self, _seed: Numerical = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ @@ -145,6 +146,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def setstate(self, _state: Numerical, /) -> None: """Restores the internal state of the generator. diff --git a/Python3.12/PyRandLib/pcg64_32.py b/Python3.12/PyRandLib/pcg64_32.py index 4eed457..5bf6e81 100644 --- a/Python3.12/PyRandLib/pcg64_32.py +++ b/Python3.12/PyRandLib/pcg64_32.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .basepcg import BasePCG from .annotation_types import Numerical from .splitmix import SplitMix64 @@ -113,6 +115,7 @@ def __init__(self, _seed: Numerical = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/splitmix.py b/Python3.12/PyRandLib/splitmix.py index 0b97a51..5e2853c 100644 --- a/Python3.12/PyRandLib/splitmix.py +++ b/Python3.12/PyRandLib/splitmix.py @@ -22,6 +22,7 @@ #============================================================================= import time +from typing import override from .annotation_types import Numerical @@ -105,6 +106,7 @@ def __init__(self, _seed: Numerical = None, /) -> None: super().__init__( _seed ) #------------------------------------------------------------------------- + @override def __call__(self, _seed: int = None, /) -> int: """The split-mix algorithm. """ @@ -131,6 +133,7 @@ def __init__(self, _seed: Numerical = None, /) -> None: super().__init__( _seed ) #------------------------------------------------------------------------- + @override def __call__(self, _seed: int = None, /) -> int: """The split-mix algorithm. """ diff --git a/Python3.12/PyRandLib/squares32.py b/Python3.12/PyRandLib/squares32.py index 268b56e..131b322 100644 --- a/Python3.12/PyRandLib/squares32.py +++ b/Python3.12/PyRandLib/squares32.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .basesquares import BaseSquares from .annotation_types import SeedStateType, StatesList @@ -86,6 +88,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. diff --git a/Python3.12/PyRandLib/squares64.py b/Python3.12/PyRandLib/squares64.py index 5efd5b5..66d15d7 100644 --- a/Python3.12/PyRandLib/squares64.py +++ b/Python3.12/PyRandLib/squares64.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .basesquares import BaseSquares from .annotation_types import SeedStateType, StatesList @@ -105,6 +107,7 @@ def __init__(self, _seedState: SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. diff --git a/Python3.12/PyRandLib/well1024a.py b/Python3.12/PyRandLib/well1024a.py index dfd7085..42140c1 100644 --- a/Python3.12/PyRandLib/well1024a.py +++ b/Python3.12/PyRandLib/well1024a.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basewell import BaseWELL @@ -113,6 +113,7 @@ class Well1024a( BaseWELL ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/well19937c.py b/Python3.12/PyRandLib/well19937c.py index 75ccee7..c5c5601 100644 --- a/Python3.12/PyRandLib/well19937c.py +++ b/Python3.12/PyRandLib/well19937c.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basewell import BaseWELL @@ -113,6 +113,7 @@ class Well19937c( BaseWELL ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/well44497b.py b/Python3.12/PyRandLib/well44497b.py index 9a14931..ffd9ffb 100644 --- a/Python3.12/PyRandLib/well44497b.py +++ b/Python3.12/PyRandLib/well44497b.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basewell import BaseWELL @@ -113,6 +113,7 @@ class Well44497b( BaseWELL ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/well512a.py b/Python3.12/PyRandLib/well512a.py index aaf532e..469c4e0 100644 --- a/Python3.12/PyRandLib/well512a.py +++ b/Python3.12/PyRandLib/well512a.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .basewell import BaseWELL @@ -114,6 +114,7 @@ class Well512a( BaseWELL ): #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. """ diff --git a/Python3.12/PyRandLib/xoroshiro1024.py b/Python3.12/PyRandLib/xoroshiro1024.py index 9a0f7a1..e69846e 100644 --- a/Python3.12/PyRandLib/xoroshiro1024.py +++ b/Python3.12/PyRandLib/xoroshiro1024.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baserandom import BaseRandom from .basexoroshiro import BaseXoroshiro @@ -105,6 +105,7 @@ def __init__(self, _seedState: Numerical | SeedStateType = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. """ @@ -119,6 +120,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def getstate(self) -> list[ int ]: """Returns an object capturing the current internal state of the generator. @@ -129,6 +131,7 @@ def getstate(self) -> list[ int ]: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: SeedStateType = None, /) -> None: """Restores the internal state of the generator. @@ -175,6 +178,7 @@ def _initindex(self, _index: int, /) -> None: #------------------------------------------------------------------------- + @override def _initstate(self, _initialSeed: Numerical = None, /) -> None: """Inits the internal list of values. diff --git a/Python3.12/PyRandLib/xoroshiro256.py b/Python3.12/PyRandLib/xoroshiro256.py index 631b301..e78af7c 100644 --- a/Python3.12/PyRandLib/xoroshiro256.py +++ b/Python3.12/PyRandLib/xoroshiro256.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import override + from .baserandom import BaseRandom from .basexoroshiro import BaseXoroshiro from .annotation_types import Numerical, StatesList @@ -103,6 +105,7 @@ def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. """ @@ -119,6 +122,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def getstate(self) -> tuple[ int ]: """Returns an object capturing the current internal state of the generator. @@ -129,6 +133,7 @@ def getstate(self) -> tuple[ int ]: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: """Restores the internal state of the generator. @@ -161,6 +166,7 @@ def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: #------------------------------------------------------------------------- + @override def _initstate(self, _initialSeed: Numerical = None, /) -> None: """Inits the internal list of values. diff --git a/Python3.12/PyRandLib/xoroshiro512.py b/Python3.12/PyRandLib/xoroshiro512.py index 10d3c0a..a8b65f0 100644 --- a/Python3.12/PyRandLib/xoroshiro512.py +++ b/Python3.12/PyRandLib/xoroshiro512.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Final +from typing import Final, override from .baserandom import BaseRandom from .basexoroshiro import BaseXoroshiro @@ -104,6 +104,7 @@ def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: #------------------------------------------------------------------------- + @override def next(self) -> int: """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. """ @@ -124,6 +125,7 @@ def next(self) -> int: #------------------------------------------------------------------------- + @override def getstate(self) -> tuple[ int ]: """Returns an object capturing the current internal state of the generator. @@ -134,6 +136,7 @@ def getstate(self) -> tuple[ int ]: #------------------------------------------------------------------------- + @override def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: """Restores the internal state of the generator. @@ -166,6 +169,7 @@ def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: #------------------------------------------------------------------------- + @override def _initstate(self, _initialSeed: Numerical = None, /) -> None: """Inits the internal list of values. From 555fe43648314f25ebe76aca0ebb7ef735321371 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 6 Mar 2025 11:53:10 +0100 Subject: [PATCH 04/14] #135-implement new random methods since Python 3.12 Completed. --- Python3.10/PyRandLib/baserandom.py | 33 ++++++++++++++++++++++++++++- Python3.11/PyRandLib/baserandom.py | 33 ++++++++++++++++++++++++++++- Python3.12/PyRandLib/baserandom.py | 10 ++++++++- Python3.6/PyRandLib/baserandom.py | 34 +++++++++++++++++++++++++++++- Python3.9/PyRandLib/baserandom.py | 33 ++++++++++++++++++++++++++++- README.md | 19 +++++++++++++++-- 6 files changed, 155 insertions(+), 7 deletions(-) diff --git a/Python3.10/PyRandLib/baserandom.py b/Python3.10/PyRandLib/baserandom.py index 469fe65..ee6dc43 100644 --- a/Python3.10/PyRandLib/baserandom.py +++ b/Python3.10/PyRandLib/baserandom.py @@ -99,7 +99,17 @@ class BaseRandom( Random ): | Choose a random element from a non-empty sequence. | | - | expovariate(self, lambd) + | binomialvariate**(self, n=1, p=0.5) + | Binomial distribution. Return the number of successes for n + | independent trials with the probability of success in each + | trial being p. + | Notice: added since Python 3.12, implemented in PyRandLib + | for all previous versions of Python. + | n >= 0, 0.0 <= p <= 1.0, + | the result is an integer in the range 0 <= X <= n. + | + | + | expovariate(self, lambd=1.0) | Exponential distribution. | | lambd is 1.0 divided by the desired mean. It should be @@ -289,7 +299,28 @@ def random(self) -> float: coded on anything else than 32 bits. """ return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def binomialvariate(self, n: int = 1, p: float = 0.5) -> int: + """Binomial distribution. Returns the number of successes for n>=0 independent trials. + + The probability of success in each trial is p, 0.0 <= p <= 1.0. + Built-in method available since Python 3.12, implemented in PyRandLib + for all former versions of Python. + """ + return sum( self.random() < p for _ in range(n) ) + + #------------------------------------------------------------------------- + def expovariate(self, lambd: float = 1.0) -> float: + """Exponential distribution. + + Since Python 3.12, a default value is assigned to the parameter of + this bult-in method. So, it is define this way in PyRandLib for all + former versions of Python. + """ + return super().expovariate(lambd) #------------------------------------------------------------------------- def getrandbits(self, k: int, /) -> int: diff --git a/Python3.11/PyRandLib/baserandom.py b/Python3.11/PyRandLib/baserandom.py index 469fe65..ee6dc43 100644 --- a/Python3.11/PyRandLib/baserandom.py +++ b/Python3.11/PyRandLib/baserandom.py @@ -99,7 +99,17 @@ class BaseRandom( Random ): | Choose a random element from a non-empty sequence. | | - | expovariate(self, lambd) + | binomialvariate**(self, n=1, p=0.5) + | Binomial distribution. Return the number of successes for n + | independent trials with the probability of success in each + | trial being p. + | Notice: added since Python 3.12, implemented in PyRandLib + | for all previous versions of Python. + | n >= 0, 0.0 <= p <= 1.0, + | the result is an integer in the range 0 <= X <= n. + | + | + | expovariate(self, lambd=1.0) | Exponential distribution. | | lambd is 1.0 divided by the desired mean. It should be @@ -289,7 +299,28 @@ def random(self) -> float: coded on anything else than 32 bits. """ return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def binomialvariate(self, n: int = 1, p: float = 0.5) -> int: + """Binomial distribution. Returns the number of successes for n>=0 independent trials. + + The probability of success in each trial is p, 0.0 <= p <= 1.0. + Built-in method available since Python 3.12, implemented in PyRandLib + for all former versions of Python. + """ + return sum( self.random() < p for _ in range(n) ) + + #------------------------------------------------------------------------- + def expovariate(self, lambd: float = 1.0) -> float: + """Exponential distribution. + + Since Python 3.12, a default value is assigned to the parameter of + this bult-in method. So, it is define this way in PyRandLib for all + former versions of Python. + """ + return super().expovariate(lambd) #------------------------------------------------------------------------- def getrandbits(self, k: int, /) -> int: diff --git a/Python3.12/PyRandLib/baserandom.py b/Python3.12/PyRandLib/baserandom.py index 5fa8a7c..4181af7 100644 --- a/Python3.12/PyRandLib/baserandom.py +++ b/Python3.12/PyRandLib/baserandom.py @@ -100,7 +100,15 @@ class BaseRandom( Random ): | Choose a random element from a non-empty sequence. | | - | expovariate(self, lambd) + | binomialvariate**(self, n=1, p=0.5) + | Binomial distribution. Return the number of successes for n + | independent trials with the probability of success in each + | trial being p. + | n >= 0, 0.0 <= p <= 1.0, + | the result is an integer in the range 0 <= X <= n. + | + | + | expovariate(self, lambd=1.0) | Exponential distribution. | | lambd is 1.0 divided by the desired mean. It should be diff --git a/Python3.6/PyRandLib/baserandom.py b/Python3.6/PyRandLib/baserandom.py index 7332741..3dafbce 100644 --- a/Python3.6/PyRandLib/baserandom.py +++ b/Python3.6/PyRandLib/baserandom.py @@ -99,8 +99,18 @@ class BaseRandom( Random ): | choice(self, seq) | Choose a random element from a non-empty sequence. | + | + | binomialvariate**(self, n=1, p=0.5) + | Binomial distribution. Return the number of successes for n + | independent trials with the probability of success in each + | trial being p. + | Notice: added since Python 3.12, implemented in PyRandLib + | for all previous versions of Python. + | n >= 0, 0.0 <= p <= 1.0, + | the result is an integer in the range 0 <= X <= n. + | | - | expovariate(self, lambd) + | expovariate(self, lambd=1.0) | Exponential distribution. | | lambd is 1.0 divided by the desired mean. It should be @@ -292,6 +302,28 @@ def random(self) -> float: return self.next() * self._NORMALIZE + #------------------------------------------------------------------------- + def binomialvariate(self, n: int = 1, p: float = 0.5) -> int: + """Binomial distribution. Returns the number of successes for n>=0 independent trials. + + The probability of success in each trial is p, 0.0 <= p <= 1.0. + Built-in method available since Python 3.12, implemented in PyRandLib + for all former versions of Python. + """ + return sum( self.random() < p for _ in range(n) ) + + + #------------------------------------------------------------------------- + def expovariate(self, lambd: float = 1.0) -> float: + """Exponential distribution. + + Since Python 3.12, a default value is assigned to the parameter of + this bult-in method. So, it is define this way in PyRandLib for all + former versions of Python. + """ + return super().expovariate(lambd) + + #------------------------------------------------------------------------- def getrandbits(self, k: int) -> int: """Returns k bits from the internal state of the generator. diff --git a/Python3.9/PyRandLib/baserandom.py b/Python3.9/PyRandLib/baserandom.py index 97d2545..4dcda14 100644 --- a/Python3.9/PyRandLib/baserandom.py +++ b/Python3.9/PyRandLib/baserandom.py @@ -100,7 +100,17 @@ class BaseRandom( Random ): | Choose a random element from a non-empty sequence. | | - | expovariate(self, lambd) + | binomialvariate**(self, n=1, p=0.5) + | Binomial distribution. Return the number of successes for n + | independent trials with the probability of success in each + | trial being p. + | Notice: added since Python 3.12, implemented in PyRandLib + | for all previous versions of Python. + | n >= 0, 0.0 <= p <= 1.0, + | the result is an integer in the range 0 <= X <= n. + | + | + | expovariate(self, lambd=1.0) | Exponential distribution. | | lambd is 1.0 divided by the desired mean. It should be @@ -290,7 +300,28 @@ def random(self) -> float: coded on anything else than 32 bits. """ return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def binomialvariate(self, n: int = 1, p: float = 0.5) -> int: + """Binomial distribution. Returns the number of successes for n>=0 independent trials. + + The probability of success in each trial is p, 0.0 <= p <= 1.0. + Built-in method available since Python 3.12, implemented in PyRandLib + for all former versions of Python. + """ + return sum( self.random() < p for _ in range(n) ) + + #------------------------------------------------------------------------- + def expovariate(self, lambd: float = 1.0) -> float: + """Exponential distribution. + + Since Python 3.12, a default value is assigned to the parameter of + this bult-in method. So, it is define this way in PyRandLib for all + former versions of Python. + """ + return super().expovariate(lambd) #------------------------------------------------------------------------- def getrandbits(self, k: int, /) -> int: diff --git a/README.md b/README.md index 67aafa7..1d5f5ef 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,10 @@ In **PyRandLib**, the Squares32 and Squares64 versions of the algorithm are impl 1. Method `bytesrand()` has been added to the Python built-in class `random.Random` since Python 3.9. So, it is also available in **PyRandLib** but for **all** its Python versions: in Python 3.6 its implementation has been added into base class `BaseRandom`. +1. Method `random.binomialvariate()`has been added to the Python built-in class `random.Random` since Python 3.12. So, it is also available in **PyRandLib** but for **all** its Python versions: in Python -3.6, -3.9, -3.10 and -3.11 its implementation has been added into base class `BaseRandom`. + +1. Since Python 3.12, a default value is now specified (i.e. 1.0) for parameter `lambd` in method `random.Random.expovariate()`. So, it is also specified now in **PyRandLib** for **all** its Python versions: in Python -3.6, -3.9, -3.10 and -3.11 its definition has been added into base class `BaseRandom`. + 1. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. It is now **highly recommended** to not use previous releases (aka. 1.x) of **PyRandLib**. @@ -594,6 +598,17 @@ Conditions on the parameters are alpha > 0 and beta > 0. Returned values range between 0 and 1. +**binomialvariate**(self, n=1, p=0.5) + +Binomial distribution. Return the number of successes for n independent trials with the probability of success in each trial being p: + +Mathematically equivalent to: + + sum(random() < p for i in range(n)) + +The number of trials n should be a non-negative integer. The probability of success p should be between 0.0 <= p <= 1.0. The result is an integer in the range 0 <= X <= n. + + **choice**(self, seq) Chooses a random element from a non-empty sequence. 'seq' has to be non empty. @@ -614,12 +629,12 @@ The `weights` or `cum_weights` can use any numeric type that interoperates with Notice: `choices` has been provided since Python 3.6. It should be implemented for older versions. -**expovariate**(self, lambd) +**expovariate**(self, lambd=1.0) Exponential distribution. `lambd` is 1.0 divided by the desired mean. It should be nonzero. (The parameter should be called "lambda", but this is a reserved word in Python). -Returned values range from 0 to positive infinity if `lambd` is positive, and from negative infinity to 0 if `lambd` is negative. +Returned values range from 0 to positive infinity if `lambd` is positive, and from negative infinity to 0 if `lambd` is negative. **gammavariate**(self, alpha, beta) From 29f075f593b8529c50b713e6ffeb0f5c6f2d6715 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 6 Mar 2025 11:58:17 +0100 Subject: [PATCH 05/14] #84-add Python 3.12 stuff in README.md Done. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d5f5ef..ad44d08 100644 --- a/README.md +++ b/README.md @@ -606,7 +606,7 @@ Mathematically equivalent to: sum(random() < p for i in range(n)) -The number of trials n should be a non-negative integer. The probability of success p should be between 0.0 <= p <= 1.0. The result is an integer in the range 0 <= X <= n. +The number of trials n should be a non-negative integer. The probability of success p should be between 0.0 <= p <= 1.0. The result is an integer in the range 0 <= X <= n. This built-in, method has been added since Python 3.12. **PyRandLIb** implements it also for all former versions of Python: -3.6, -3.9, -3.10, and -3.11. **choice**(self, seq) @@ -635,6 +635,7 @@ Exponential distribution. `lambd` is 1.0 divided by the desired mean. It should be nonzero. (The parameter should be called "lambda", but this is a reserved word in Python). Returned values range from 0 to positive infinity if `lambd` is positive, and from negative infinity to 0 if `lambd` is negative. +Since Python 3.12, the parameter `lambd` gets a default value in this built-in method. **PyRandLib** defines then this method for all former versions of Pyhton : -3.6, -3.9, -3.10 and -3.11. **gammavariate**(self, alpha, beta) From 08cdc5ded3accb76cc85fea6a03d8e132c026ca0 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 6 Mar 2025 13:55:32 +0100 Subject: [PATCH 06/14] #137-text review after Py3.12 implementation Done. Many fixes. --- README.md | 220 +++++++++++++++++++++++++----------------------------- 1 file changed, 100 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index ad44d08..54b1a6e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,9 @@ SOFTWARE. ## Intro This library implements some of the best-in-class pseudo random generators as evaluated by Pierre L'Ecuyer and Richard Simard in their famous paper "TestU01: A C library for empirical testing of random number generators" (ACM Trans. Math. Softw. Vol. 33 N.4, August 2007 - see reference [1]. The reader will take benefit reading L'Ecuyer & Simard's paper. -Each of the Pseudo Random Numbers Generator(PRNG) implemented in **PyRandLib** is self documented. Names of classes directly refer to the type of PRNG they implement augmented with some number characterizing their periodicity. All of their randomness characteristics are explained in every related module. +Each of the Pseudo Random Numbers Generator (PRNG) implemented in **PyRandLib** is self documented. Names of classes directly refer to the type of PRNG they implement augmented with some number characterizing their periodicity. All of their randomness characteristics are explained in every related module. + +Latest version of **PyRandLib** is version **2.0**, released by March 2025. It provides additional implementations of recent pseudo-random generators with very good randomness characteristics. It provides also implementations dedicated to different versions of Python: 3.6 (the original version of the library), 3.9, 3.10, 3.11, 3.12 and 3.13. Time performances of every PRNG and for each version of Python (starting at 3.9) have been evaluated and are provided in a table below - see section *CPU Performances*. ### Why not Mersenne twister? @@ -43,14 +45,16 @@ The Mersenne twister PRNG proposed by Matsumoto and Nishimura - see [5] - is th implements this PRNG. It is also implemented in C++ and Java standard libraries for instance. -It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, this PRNG is a little bit long to compute (up to 3 times than LCGs, 60% more than LFibs and a little bit less than MRGs, see below at section 'Architecture overview'). Moreover, it fails four of the hardest TestU01 tests. You can still use it as your preferred PRNG but **PyRandLib** implements many other PRNGs that are either far faster or far better in terms of generated pseudo-randomness than the Mersenne twister PRG. +It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, this PRNG is a little bit long to compute (up to 3 times than LCGs, 60% more than LFibs and a little bit less than MRGs, see below at section 'Architecture overview'). Moreover, it fails four of the hardest TestU01 tests. You can still use it as your preferred PRNG but **PyRandLib** implements many other PRNGs that are either far faster or far better in terms of generated pseudo-randomness than the Mersenne twister PRNG. ## Installation Currently, the only way to install **PyRandLib** is to download the `.zip` or `.tar.gz` archive, then to directly put sub-directory `PyRandLib\` from archive into directory `Lib\site-packages\` of your Python environment. See https://schmouk.github.io/PyRandLib/ for an easy access to download versions or click on tab **releases** on the home page of this GitHub repository. -A distribution version (to be installed via pip or easy-install in cmd tool or in console) is to come (no date yet). +Since release **2.0** of **PyrandLib**, the root directory of the library is splitted into directories dedicated each to a different version of Python (3.6, 3.9, 3.10, etc.) Directory `PyRandLib\` is now a sub-directory of each of these directories, with code optimized for the related Python version. Just copy into your dev environment the `PyRandLib\` directory from the version of Python of your choice. + +Notice: distribution version to be installed via pip or easy-install in cmd tool or in console is to come (no date yet). @@ -89,27 +93,32 @@ We add in this table the evaluations provided by the authors of every new PRNGs | Well1024a | WELL1024a | 32 x 4-bytes | 2^1,024 | 4.0 | 1.1 | 0 | 4 | 4 | | Well19937b (2) | WELL19937a | 624 x 4-bytes | 2^19,937 | 4.3 | 1.3 | 0 | 2 | 2 | | Well44497c | not available | 1,391 x 4-bytes | 2^44,497 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19,937 | 4.30 | 1.6 | 0 | 2 | 2 | + | Mersenne twister | MT19937 | 624 x 4-bytes | 2^19,937 | 4.30 | 1.6 | 0 | 2 | 2 | | Xiroshiro256 | *xiroshiro256*** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | | Xiroshiro512 | *xiroshiro512*** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | | Xiroshiro1024 | *xiroshiro1024*** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | -(1)*or the generator original name in the related paper* -(2)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. +(1) *or the generator original name in the related paper* +(2) The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. ## CPU Performances - Times evaluation -The above table provided times related to the C implementation of the specified PRNGs as measured with TestU01 [1] by the authors of the paper. -We provide in the table below the evaluation of times spent in calling the `__call__()` method for all PRNGs implemented in library **PyRandLib**. Then, the measured elapsed time includes the calling and returning Python mechanisms and not only the computation time of the sole algorithm code. This is the duration of interest to you since this is the main use of the library you will have. It only helps comparing the performances between the implemented PRNGs. +The above table provides times related to the C implementation of the specified PRNGs as measured with TestU01 [1] by the authors of the paper. +We provide in the table below the evaluation of times spent in calling the `__call__()` method for all PRNGs implemented in library **PyRandLib**. Then, the measured elapsed time includes the calling and returning Python mechanisms and not only the computation time of the sole algorithm code. This is the duration of interest to you since this is the main use of the library you will have. It only helps comparing the performances between the implemented PRNGs and between the Python different versions. -We currently provide them as tested with Python 3.9. We will further provide them for every version of Python above 3.9. Time unit is microsecond. Tests have been run on an Intel(R) Core(TM) i5-1035G1 CPU @ 1.00 GHz, 1190 MHz, 4 cores, 8 logical processors, 64-bits, with 8 GB RAM and over Microsoft Windows 11 ed. Family. -The evaluation script is provided at the root of this repository: `testCPUPerfs.py`. +Time unit is microsecond. Tests have been run on an Intel(R) Core(TM) i5-1035G1 CPU @ 1.00 GHz, 1190 MHz, 4 cores, 8 logical processors, 64-bits, with 8 GB RAM and over Microsoft Windows 11 ed. Family. +The evaluation script is provided at the root of **PyRandLib** repository: `testCPUPerfs.py`. -Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environment. Measurements with next versions of Python are to come. +The Python versions used for these evaluations in their related virtual environment are (all 64-bits): +* 3.9.21 (Dec.3, 2024) +* 3.10.16 (Dec.3, 2024) +* 3.11.11 (Dec.3, 2024) +* 3.12.9 (Feb. 4, 2025) +* 3.13.2 (Feb. 4, 2025) -**PyRandLib** time-64 bits: +**PyRandLib** time-64 bits table: | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | | Cwg64 | 0.83 | 0.77 | 0.87 | | | *0* | *0* | *0* | @@ -140,7 +149,7 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme | Xiroshiro512 | 2.94 | 2.81 | 2.72 | | | *0* | *0* | *0* | | Xiroshiro1024 | 2.78 | 2.59 | 2.41 | | | *0* | *0* | *0* | -(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with a *tempering* algorithm. +(1) The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with a *tempering* algorithm. (*missing values in empty columns are to come*) ## Implementation @@ -151,7 +160,9 @@ Note 1: **PyRandLib** version 1.1 and below should work with all versions of Pyt Note 2: no version or **PyRandLib** will ever be provided for Python 2 which is a no more maintained version of the Python language. -Note 3: a Cython version of PyRandLib might be delivered in a next release. Up today, no date is planned for this. +Note 3: since release **2.0** of **PyRandLib** directories have been created that are each dedicated to a version of Python : 3.6, 3.9, 3.10, etc. Each of these directories contains the sub-directory `PyRandLib\` with a specific implementation of the library, optimized for the version of Python it relates to. + +Note 4: a Cython version of **PyRandLib** will be delivered in a next major release (i.e. 3.0). Up today, no date is planned for this. ## New in release 1.2 @@ -190,44 +201,44 @@ The call operator (i.e., '()') gets a new signature which is still backward com ## New in release 2.0 -Version 2.0 of **PyRandLib** implements some new other "recent" PRNGs - see them listed below. It also provides two test scripts, enhanced documentation and some other internal development features. Finally, it is splitted in many subdirectories each dedicated to a specific version of Python: Python3.6, Python3.9, Python3.10, etc. In each of these directories, library **yRandLib** code is fully copied and modified to take benefit of the improvements on new Python versions syntax and features. Download the one version of value for your application to get all **PyRandLib** stuff at its best for your needs. +Version 2.0 of **PyRandLib** implements some new other "recent" PRNGs - see them listed below. It also provides two test scripts, enhanced documentation and some other internal development features. Finally, it is splitted in many subdirectories each dedicated to a specific version of Python: Python3.6, Python3.9, Python3.10, etc. In each of these directories, library **PyRandLib** code is fully copied and modified to take benefit of the improvements on new Python versions syntax and features. Copy the one version of value for your application to get all **PyRandLib** stuff at its best for your needs. -Major 2.0 novelties are listed below: +**Major 2.0 novelties are listed below:** 1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6], 2006) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (up to 1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. -In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b which all generate output values coded on 32-bits. +In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b - they all generate output values coded on 32-bits. -1. The PCG algorithm (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and clearly explained in technical report [7]. It offers jumping, hard to discover internal state and multi-streams featured. It passes all crush and big crush tests of TestU01. -**PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities: Pcg64_32, Pcg128-64 and Pcg1024_32 classes which generate output values coded on resp. 32-, 64- and 32- bits. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). +1. The PCG algorithm (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and is clearly explained in technical report [7]. It offers jumping ahead, a hard to discover its internal state characteristic, and multi-streams feature. It passes all crush and big crush tests of TestU01. +**PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities: Pcg64_32, Pcg128_64 and Pcg1024_32 classes which generate output values coded on resp. 32-, 64- and 32- bits. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can its code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). -1. The CWG algorithm (Collatz-Weyl Generator, see [8], 2024) is now implemented in **PyRandLib**. This algorithm is fast, uses four integers as its internal state and generates chaos via multiplication and xored-shifted instructions. Periods are medium to large and the generated randomness is of up quality. It does not offer jump ahead but multi-streams feature is available via the simple modification of well specified one of the four integers. -In **PyRandLib**, the CWG algorithm is provided in next forms: Cwg64, Cwg64-128 and Cwg128 which generate output values coded on resp. 64-, 64- and 128- bits . +1. The CWG algorithm (Collatz-Weyl Generator, see [8], 2024) is now implemented in **PyRandLib**. This algorithm is fast, uses four integers as its internal state and generates chaos via multiplication and xored-shifted instructions. Periods are medium to large and the generated randomness is of up quality. It does not offer jump ahead but multi-streams feature is available via the simple modification of a well specified integer of its four integers state. +In **PyRandLib**, the CWG algorithm is provided in next forms: Cwg64, Cwg64-128 and Cwg128 that generate output values coded on resp. 64-, 64- and 128- bits . 1. The Squares algorithm (see "Squares: A Fast Counter-Based RNG" [9], 2022) is now implemented in **PyRandLib**. This algorithm is fast, uses two 64-bits integers as its internal state (a counter and a key), gets a period of 2^64 and runs through 4 to 5 rounds of squaring, exchanging high and low bits and xoring intermediate values. Multi-streams feature is available via the value of the key. -In **PyRandLib**, the Squares32 and Squares64 versions of the algorithm are implemented, which provide resp. 32- and 64- bits output values. Caution: the 64-bits versions should not pass the birthday test, which is a randmoness issue, while this is not mentionned in the original paper [9]. +In **PyRandLib**, the Squares32 and Squares64 versions of the algorithm are implemented. They provide resp. 32- and 64- bits output values. Caution: the 64-bits versions should not pass the birthday test, which is a randmoness issue, while this is not mentionned in the original paper [9]. -1. The xiroshiro algorithm ("Scrambled Linear Pseudorandom Number Generators", see [10], 2018) is now implemented in **PyRandLib**, in its *mult-mult* form for the output scrambler. This algorithm is fast, uses 64-bits integers as its internal state and outputs 64-bits values. It uses few memory space (4, 8 or 16 64-bits integers for resp. the 256-, 512- and 1024- versions that are implemented in **PyRandLib**. Notice: the 256 version of the algorithm is know to show close repeats flaws, with a bad Hamming weight near zero. *xoroshiro512* seems to best fit this property, according to the tables proposed by the authors in [10]. +1. The xoroshiro algorithm ("Scrambled Linear Pseudorandom Number Generators", see [10], 2018) is now implemented in **PyRandLib**, in its *mult-mult* form for the output scrambler. This algorithm is fast, uses 64-bits integers as its internal state and outputs 64-bits values. It uses few memory space (4, 8 or 16 64-bits integers for resp. its 256-, 512- and 1024- versions that are implemented in **PyRandLib**. Notice: the 256 version of the algorithm is know to show close repeats flaws, with a bad Hamming weight near zero. *xoroshiro512* seems to best fit this property, according to the tables proposed by the authors in [10]. -1. The MELG algorithm ("Maximally Equidistributed Long-period Linear Generators", see [11], 2018) is now implemented in **PyRandLib**. It can be considered as an extension of the WELL algorithm, with a miximization of the equidistribution of generated values, making computations on 64-bits integers and outputing 64-bits values. +2. The MELG algorithm ("Maximally Equidistributed Long-period Linear Generators", see [11], 2018) is now implemented in **PyRandLib**. It can be considered as an extension of the WELL algorithm, with a maximization of the equidistribution of generated values, making computations on 64-bits integers and outputing 64-bits values. **PyRandLib** implements its versions numbered 627-64, 19937-64 and 44497-64 related to the power of 2 of their periods: Melg627, Melg19937 and Melg44497. -1. The SplitMix algorithm is now implemented in **PyRandLib**. It is used to initialize the internal state of all other PRNGs. It SHOULD NOT be used as a PRNG due to its random poorness. +1. The SplitMix algorithm is now implemented in **PyRandLib**. It is used to initialize the internal state of all other PRNGs. It SHOULD NOT be used as a PRNG due to its random properties poorness. -1. Method `bytesrand()` has been added to the Python built-in class `random.Random` since Python 3.9. So, it is also available in **PyRandLib** but for **all** its Python versions: in Python 3.6 its implementation has been added into base class `BaseRandom`. +1. Method `bytesrand()` has been added to the Python built-in class `random.Random` since Python 3.9. So, it is also available in **PyRandLib** for **all** its Python versions: in Python 3.6 its implementation has been added into base class `BaseRandom`. -1. Method `random.binomialvariate()`has been added to the Python built-in class `random.Random` since Python 3.12. So, it is also available in **PyRandLib** but for **all** its Python versions: in Python -3.6, -3.9, -3.10 and -3.11 its implementation has been added into base class `BaseRandom`. +1. Method `random.binomialvariate()`has been added to the Python built-in class `random.Random` since Python 3.12. So, it is also available in **PyRandLib** for **all** its Python versions: in Python -3.6, -3.9, -3.10 and -3.11 its implementation has been added into base class `BaseRandom`. -1. Since Python 3.12, a default value is now specified (i.e. 1.0) for parameter `lambd` in method `random.Random.expovariate()`. So, it is also specified now in **PyRandLib** for **all** its Python versions: in Python -3.6, -3.9, -3.10 and -3.11 its definition has been added into base class `BaseRandom`. +1. Since Python 3.12, a default value has been specified (i.e. = 1.0) for parameter `lambd` in method `random.Random.expovariate()`. So, it is also specified now in **PyRandLib** for **all** its Python versions: in Python -3.6, -3.9, -3.10 and -3.11 its definition has been added into base class `BaseRandom`. -1. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. -It is now **highly recommended** to not use previous releases (aka. 1.x) of **PyRandLib**. +1. A short script `testED.py` is now avalibale at root directory. It checks the equidistribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. +It is now **highly recommended** to not use previous releases of **PyRandLib** (aka. 1.x). -1. Another short script `testCPUPerfs.py` is now avaliable for testing CPU performance of the different implemented algorithms. It has been used to enhance this documentation by providing a new *times evaluation* table. +1. Another short script `testCPUPerfs.py` is now avaliable for testing CPU performance of the different implemented algorithms. It has been used to enhance this documentation by providing a new *CPU Evaluations* table. 1. Documentation has been enhanced, with typos and erroneous docstrings fixed also. -1. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. -So, if you want to see what is currently going on for next release, just check-out branch `dev`. +1. All developments are now done under a newly created branch named `dev` (GitHub). This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` should only happen when creating new releases. +So, if you want to see what is currently going on for next release of **PyRandLib**, just check-out branch `dev`. 1. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. @@ -256,7 +267,7 @@ and may override the next three methods: Notice: starting at PyRandLib 1.2.0, a new signature is available with this base class. See previous section 'New in release 1.2' for full explanations. -Notice: Since PyRandLib 2.0, class `BaseRandom` implements the new method `next()` which is substituted to `random()`. `next()` should now contains the only core of the pseudo-random numbers generator while `random()` calls it to return a float value in the interval [0.0, 1.0) just as previous versions of the library. +Notice: Since PyRandLib 2.0, class `BaseRandom` implements the new method `next()` which is substituted to `random()`. `next()` should now contain the core of the pseudo-random numbers generator while `random()` calls it to return a float value in the interval [0.0, 1.0), just as did all previous versions of the library. Since version 2.0 of PyRandLib also, the newly implemented method `getrandbits()` overrides the same method of Python built-in base class `random.Random`. @@ -272,9 +283,6 @@ This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as x(i) = ((x(i-1) >> 1) * ((a(i)) | 1)) ^ (weyl(i))) output(i) = (a(i) >> 48) ^ x(i) -See Cwg128_64 for a (minimum) 2^71 period (i.e. about 2.36e+21) and one 128-bits plus three 64-bits integers internal state. -See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits integers internal state. - ### Cwg128_64 - minimum 2^71 period @@ -287,9 +295,6 @@ This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as x(i) = ((x(i-1) | 1) * (a(i) >> 1)) ^ (weyl(i)) output(i) = (a(i) >> 48) ^ x(i) -See Cwg64 for a (minimum) 2^70 period (i.e. about 1.18e+21) and four 64-bits integers internal state. -See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits integers internal state. - ### Cwg128 - minimum 2^135 period @@ -303,9 +308,6 @@ This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as x(i) = ((x(i-1) >> 1) * ((a(i)) | 1)) ^ (weyl(i))) output(i) = (a(i) >> 96) ^ x(i) -See Cwg64 for a (minimum) 2^70 period (i.e. about 1.18e+21) and four 64-bits integers internal state. -See Cwg128_64 for a (minimum) 2^71 period (i.e. about 2.36e+21) and one 128-bits plus three 64-bits integers internal state. - ### FastRand32 - 2^32 periodicity @@ -321,8 +323,6 @@ The implementation of **FastRand32** is based on (*a*=69069, *c*=1) since thes Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use such pseudo-random numbers generators for serious simulation applications. -See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with low computation time and *better* randomness characteristics. - ### FastRand63 - 2^63 periodicity @@ -337,8 +337,6 @@ The implementation of this LCG 63-bits model is based on (*a*=921974142649997144 Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests than does FastRandom32. -See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% lower computation time. - ### LFibRand78 - 2^78 periodicity @@ -361,7 +359,7 @@ The implementation of **LFibRand78** is based on a Lagged Fibonacci generator ( It offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due to the use of a 2^64 modulo (less than twice the computation time of LCGs) and low memory consumption (17 integers 32-bits coded). -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. +Please notice that the TestU01 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. @@ -373,7 +371,7 @@ Please notice that the TestUO1 article states that the operator should be '*' wh It offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due to the use of a 2^64 modulo (less than twice the computation time of LCGs) and some memory consumption (55 integers 32-bits coded). -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. +Please notice that the TestU01 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. @@ -385,7 +383,7 @@ Please notice that the TestUO1 article states that the operator should be '*' wh It offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due to the use of a 2^64 modulo (less than twice the computation time of LCGs) and much memory consumption (607 integers 32-bits coded). -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. +Please notice that the TestU01 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. @@ -397,7 +395,7 @@ Please notice that the TestUO1 article states that the operator should be '*' wh It offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time due to the use of a 2^64 modulo (less than twice the computation time of LCGs) and much more memory consumption (1279 integers 32-bits coded). -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. +Please notice that the TestU01 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. @@ -405,15 +403,15 @@ Please notice that the TestUO1 article states that the operator should be '*' wh **Melg627** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a large period (2^627, i.e. 5.31e+182) and low computation time. The internal state of this PRNG is equivalent to 21 integers 32-bits coded. -The base MELG algorithm offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations +The base MELG algorithm mixes, xor's and shifts its internal state and offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations ### Melg19937 -- 2^19937 periodicity -**Melg19937** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a large period (2^19,937, i.e. 4.32e+6001) and low computation time. The internal state of this PRNG is equivalent to 625 integers 32-bits coded. +**Melg19937** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a large period (2^19,937, i.e. 4.32e+6,001) and low computation time. The internal state of this PRNG is equivalent to 625 integers 32-bits coded. -The base MELG algorithm offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations +The base MELG algorithm mixes, xor's and shifts its internal state and offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations @@ -421,7 +419,7 @@ The base MELG algorithm offers large to very large periods with the best known r **Melg44497** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a very large period (2^44,497, i.e. 15.1e+13,466) and low computation time. The internal state of this PRNG is equivalent to 1.393 integers 32-bits coded. -The base MELG algorithm offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations +The base MELG algorithm mixes, xor's and shifts its internal state and offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations @@ -433,7 +431,7 @@ Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random nu x(i) = A * SUM[ x(i-k) ] mod M -MRGs offer very large periods with the best known results in the evaluation of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard. It is therefore strongly recommended to use such pseudo-random numbers generators rather than LCG ones for serious simulation applications. +MRGs offer very large periods with the best known results in the evaluation of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in [1]. It is therefore strongly recommended to use such pseudo-random numbers generators rather than LCG ones for serious simulation applications. The implementation of this specific MRG 32-bits model is finally based on a Lagged Fibonacci generator (LFIB), the Marsa-LFIB4 one. @@ -463,6 +461,8 @@ The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) +See Mrg for an explanation of the MRG original algorithm. + ### Mrg49507 - 2^49,507 periodicity @@ -473,31 +473,34 @@ The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG p x(i) = (-2^25-2^7) * ( x(i-7) + x(i-1597) ) mod (2^31-1) +See Mrg for an explanation of the MRG original algorithm. + ### Pcg64_32 - 2^64 periodicity **Pcg64_32** implements a fast 64-bits state and 32-bits output Permutated Congruential Generator with a medium period (2^64, i.e. 1.84e+19) with low computation time and very small memory space consumption (2 integers 32-bits coded). -The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is hard to be reverted and predicted. +The underlying algorithm acts as an LCG associated with a bits permutation as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is hard to be reverted and predicted. **PyRandLib** implements for ths the *PCG XSH RS 64/32 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. + ### Pcg128_64 - 2^128 periodicity **Pcg128_64** implements a fast 128-bits state and 64-bits output Permutated Congruential Generator with a medium period (2^128, i.e. 3.40e+38) with low computation time and very small memory space consumption (4 integers 32-bits coded). -The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. +The underlying algorithm acts as an LCG associated with a bits permutation as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. **PyRandLib** implements for this the *PCG XSL RR 128/64 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. ### Pcg1024_32 - 2^32,830 periodicity -**Pcg1024_32** implements a fast 64-bits based state and 32-bits output Permutated Congruential Generator with a very large period (2^32,830, i.e. 6.53e+9882) with low computation time and large memory space consumption (1,026 integers 32-bits coded). +**Pcg1024_32** implements a fast 64-bits based state and 32-bits output Permutated Congruential Generator with a very large period (2^32,830, i.e. 6.53e+9,882) with low computation time and large memory space consumption (1,026 integers 32-bits coded). -The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value, and an array of 32-bits independant MCG (multiplied congruential generators) used to create huge chaos. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. +The underlying algorithm acts as an LCG associated with a bits permutation as its final step before outputing next random value, and an array of 32-bits independant MCG (multiplied congruential generators) used to create huge chaos. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. **PyRandLib** implements for this the *PCG XSH RS 64/32 (EXT 1024)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. @@ -531,15 +534,15 @@ Meanwhile, it should not be able to pass some of the *crush* and *big-crush* tes **Well1024a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^1024 - i.e. 2.68+308 - with short computation time and 32 integers 32-bits coded memory consumption. +It offers a long period of value 2^1,024 - i.e. 2.68+308 - with short computation time and 32 integers 32-bits coded memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it does not pass 4 of the *crush* and 4 of the *big-crush* tests of TestU01. -### Well199937b - 2^19,937 periodicity +### Well199937c - 2^19,937 periodicity -**Well199937b** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. +**Well199937c** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. It offers a long period of value 2^19,937 - i.e. 4.32e+6,001 - with short computation time and 624 integers 32-bits coded memory consumption - just s the Mersenne-Twister algorithm). It escapes the zeroland at a very fast pace. @@ -547,22 +550,22 @@ Meanwhile, it does not pass 2 of the *crush* and 2 of the *big-crush* tests of T -### Well44497c - 2^44,497 periodicity +### Well44497b - 2^44,497 periodicity -**WellWell44497c** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. +**WellWell44497b** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. It offers a long period of value 2^44,497 - i.e. 1.51e+13,466 - with short computation time and 1,391 integers 32-bits coded memory consumption. It escapes the zeroland at a fast pace. -Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush* tests of TestU01, while it can be expected to better behave than the Well19937b version - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. +Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush* tests of TestU01, while it can be expected to better behave than the Well19937c version - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. ### Xoroshiro256 - 2^256 periodicity -**Xoroshiro256** implements version *xoroshiro256*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 4 integers state array. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. +**Xoroshiro256** implements version *xoroshiro256*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in [10]. This xoroshiro linear transformation updates cyclically two words of a 4 integers state array. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. It offers a medium period of value 2^256 - i.e. 1.16e+77 - with short computation time and 4 integers 64-bits coded memory consumption. -It escapes the zeroland at a fast pace (about 10 loops) and offers jump-ahead feature. Notice: the 256 version of the algorithm has shown close repeats flaws, with a bad Hamming weight near zero - see [https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html](https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). +It escapes the zeroland at a very fast pace (about 10 loops) and offers jump-ahead feature. Notice: the 256 version of the algorithm has shown close repeats flaws, with a bad Hamming weight near zero as explained by the authors in [10] and explained in [https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html](https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). @@ -571,35 +574,33 @@ It escapes the zeroland at a fast pace (about 10 loops) and offers jump-ahead fe **Xoroshiro512** implements version *xoroshiro512*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 8 integers state array. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. It offers a medium period of value 2^512 - i.e. 1.34e+154 - with short computation time and 4 integers 64-bits coded memory consumption. -It escapes the zeroland at a fast pace (about 30 loops) and offers jump-ahead feature. +It escapes the zeroland at a very fast pace (about 30 loops) and offers jump-ahead feature. -### Xoroshiro1024 - 2^1024 periodicity +### Xoroshiro1024 - 2^1,024 periodicity -**Xoroshiro512** implements version *xoroshiro1024*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 16 integers state array and a 4 bits index. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. +**Xoroshiro** implements version *xoroshiro1024*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 16 integers state array and a 4 bits index. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. -It offers a medium period of value 2^1024 - i.e. 1.80e+308 - with short computation time and 4 integers 64-bits coded memory consumption. +It offers a medium period of value 2^1,024 - i.e. 1.80e+308 - with short computation time and 4 integers 64-bits coded memory consumption. It escapes the zeroland at a fast pace (about 100 loops) and offers jump-ahead feature. ## Inherited Distribution and Generic Functions -(some of next explanation may be free to exact copy of Python 3.6 documentation. See [https://docs.python.org/3.6/library/random.html?highlight=random#module-random](https://docs.python.org/3.6/library/random.html?highlight=random#module-random)) +(some of next explanation may be free to exact copy of Python documentation. -Since the base class **BaseRandom** inherits from the built-in class random.Random, every PRNG class of **PyRandLib** gets automatic access to the next distribution and generic methods: +Since the base class **BaseRandom** inherits from the built-in class `random.Random`, every PRNG class of **PyRandLib** gets automatic access to the next distribution and generic methods: -**betavariate**(self, alpha, beta) - +**betavariate**(self, alpha, beta) Beta distribution. Conditions on the parameters are alpha > 0 and beta > 0. Returned values range between 0 and 1. -**binomialvariate**(self, n=1, p=0.5) - +**binomialvariate**(self, n=1, p=0.5) Binomial distribution. Return the number of successes for n independent trials with the probability of success in each trial being p: Mathematically equivalent to: @@ -609,13 +610,11 @@ Mathematically equivalent to: The number of trials n should be a non-negative integer. The probability of success p should be between 0.0 <= p <= 1.0. The result is an integer in the range 0 <= X <= n. This built-in, method has been added since Python 3.12. **PyRandLIb** implements it also for all former versions of Python: -3.6, -3.9, -3.10, and -3.11. -**choice**(self, seq) - +**choice**(self, seq) Chooses a random element from a non-empty sequence. 'seq' has to be non empty. -**choices**(population, weights=None, *, cum_weights=None, k=1) - +**choices**(population, weights=None, *, cum_weights=None, k=1) Returns a *k* sized list of elements chosen from the population, with replacement. If the population is empty, raises IndexError. If a *weights* sequence is specified, selections are made according to the relative weights. Alternatively, if a *cum_weights* sequence is given, the selections are made according to the cumulative weights (perhaps computed using `itertools.accumulate()`). @@ -629,8 +628,7 @@ The `weights` or `cum_weights` can use any numeric type that interoperates with Notice: `choices` has been provided since Python 3.6. It should be implemented for older versions. -**expovariate**(self, lambd=1.0) - +**expovariate**(self, lambd=1.0) Exponential distribution. `lambd` is 1.0 divided by the desired mean. It should be nonzero. (The parameter should be called "lambda", but this is a reserved word in Python). @@ -638,15 +636,13 @@ Returned values range from 0 to positive infinity if `lambd` is positive, and fr Since Python 3.12, the parameter `lambd` gets a default value in this built-in method. **PyRandLib** defines then this method for all former versions of Pyhton : -3.6, -3.9, -3.10 and -3.11. -**gammavariate**(self, alpha, beta) - +**gammavariate**(self, alpha, beta) Gamma distribution. Not the gamma function! Conditions on the parameters are `alpha` > 0 and `beta` > 0. -**gauss**(self, mu, sigma) - +**gauss**(self, mu, sigma) Gaussian distribution. mu is the mean, and sigma is the standard deviation. @@ -655,52 +651,43 @@ This is slightly faster than the normalvariate() function. Not thread-safe without a lock around calls. -**getrandbits(self, k)** - +**getrandbits**(self, k) Returns a Python integer with k random bits. Inheriting generators may also provide it as an optional part of their API. When available, `getrandbits()` enables `randrange()` to handle arbitrarily large ranges. -**getstate**(self) - +**getstate**(self) Returns internal state; can be passed to `setstate()` later. -**lognormvariate**(self, mu, sigma) - +**lognormvariate**(self, mu, sigma) Log normal distribution. If you take the natural logarithm of this distribution, you'll get a normal distribution with mean `mu` and standard deviation `sigma`. `mu` can have any value, and `sigma` must be greater than zero. -**normalvariate**(self, mu, sigma) - +**normalvariate**(self, mu, sigma) Normal distribution. `mu` is the mean, and `sigma` is the standard deviation. See method `gauss()` for a faster but not thread-safe equivalent. -**paretovariate**(self, alpha) - +**paretovariate**(self, alpha) Pareto distribution. `alpha` is the shape parameter. -**randint**(self, a, b) - +**randint**(self, a, b) Returns a random integer in range [a, b], including both end points. -**randrange**(self, stop) - -**randrange**(self, start, stop=None, step=1) - +**randrange**(self, stop) +**randrange**(self, start, stop=None, step=1) Returns a randomly selected element from range(start, stop, step). This is equivalent to `choice( range(start, stop, step) )` without building a range object. The positional argument pattern matches that of `range()`. Keyword arguments should not be used because the function may use them in unexpected ways. -**sample**(self, population, k) - +**sample**(self, population, k) Chooses `k` unique random elements from a population sequence or set. Returns a new list containing elements from the population while leaving the original population unchanged. The resulting list is in selection order so that all sub-slices will also be valid random samples. This allows raffle winners (the sample) to be partitioned into grand prize and second place winners (the subslices). @@ -710,8 +697,7 @@ Members of the population need not be hashable or unique. If the population cont To choose a sample in a range of integers, use range as an argument. This is especially fast and space efficient for sampling from a large population: `sample(range(10_000_000), 60)`. -**seed**(self, a=None, version=2) - +**seed**(self, a=None, version=2) Initialize internal state from hashable object. None or no argument seeds from current time, or from an operating system specific randomness source if available. @@ -721,13 +707,11 @@ For version 2 (the default), all of the bits are used if `a` is a str, bytes, or If `a` is an int, all bits are used. -**setstate**(self, state) - +**setstate**(self, state) Restores internal state from object returned by `getstate()`. -**shuffle**(self, x, random=None) - +**shuffle**(self, x, random=None) Shuffle the sequence x in place. Returns None. The optional argument `random` is a 0-argument function returning a random float in [0.0, 1.0); by default, this is the function random(). @@ -737,8 +721,7 @@ To shuffle an immutable sequence and return a new shuffled list, use `sample(x, Note that even for small `len(x)`, the total number of permutations of `x` can quickly grow larger than the period of most random number generators. This implies that most permutations of a long sequence can never be generated. For example, a sequence of length 2080 is the largest that can fit within the period of the Mersenne Twister random number generator. -**triangular**(self, low=0.0, high=1.0, mode=None) - +**triangular**(self, low=0.0, high=1.0, mode=None) Triangular distribution. Continuous distribution bounded by given lower and upper limits, and having a given mode value in-between. Returns a random floating point number *N* such that `low` <= *N* <= `high` and with the specified mode between those bounds. The `low` and `high` bounds default to zero and one. The mode argument defaults to the midpoint between the bounds, giving a symmetric distribution. @@ -746,20 +729,17 @@ Continuous distribution bounded by given lower and upper limits, and having a gi see [http://en.wikipedia.org/wiki/Triangular_distribution](http://en.wikipedia.org/wiki/Triangular_distribution) -**uniform**(self, a, b) - +**uniform**(self, a, b) Gets a random number in the range [`a`, `b`) or [`a`, `b`] depending on rounding. -**vonmisesvariate**(self, mu, kappa) - +**vonmisesvariate**(self, mu, kappa) Circular data distribution. `mu` is the mean angle, expressed in radians between `0` and `2*pi`, and `kappa` is the concentration parameter, which must be greater than or equal to zero. If `kappa` is equal to zero, this distribution reduces to a uniform random angle over the range `0` to `2*pi`. -**weibullvariate**(self, alpha, beta) - +**weibullvariate**(self, alpha, beta) Weibull distribution. `alpha` is the scale parameter and `beta` is the shape parameter. From eb0947e1d9c10d79e80a20d97dfa3fbfa669cd59 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 6 Mar 2025 14:25:09 +0100 Subject: [PATCH 07/14] #137-text review after Py3.12 implementation Fixed two typos. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 54b1a6e..8cd6e11 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,7 @@ The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) -See Mrg for an explanation of the MRG original algorithm. +See Mrg287 above description for an explanation of the MRG original algorithm. @@ -473,7 +473,7 @@ The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG p x(i) = (-2^25-2^7) * ( x(i-7) + x(i-1597) ) mod (2^31-1) -See Mrg for an explanation of the MRG original algorithm. +See Mrg287 above description for an explanation of the MRG original algorithm. From f0ad2e2a2f3bfcd5442e4e3017e312b846a0634b Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 6 Mar 2025 22:33:22 +0100 Subject: [PATCH 08/14] #137-text review after Py3.12 implementation Added Python 3.12 perfs. --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8cd6e11..506ca69 100644 --- a/README.md +++ b/README.md @@ -121,33 +121,33 @@ The Python versions used for these evaluations in their related virtual environm **PyRandLib** time-64 bits table: | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | - | Cwg64 | 0.83 | 0.77 | 0.87 | | | *0* | *0* | *0* | - | Cwg128_64 | 0.85 | 0.80 | 0.91 | | | *0* | *0* | *0* | - | Cwg128 | 0.94 | 0.94 | 0.99 | | | *0* | *0* | *0* | - | FastRand32 | 0.27 | 0.27 | 0.26 | | | *11* | *106* | *too many* | - | FastRand63 | 0.30 | 0.29 | 0.29 | | | *0* | *5* | *7* | - | LFib78 | 0.52 | 0.50 | 0.51 | | | *0* | *0* | *0* | - | LFib116 | 0.53 | 0.52 | 0.51 | | | *0* | *0* | *0* | - | LFib668 | 0.56 | 0.54 | 0.53 | | | *0* | *0* | *0* | - | LFib1340 | 0.59 | 0.56 | 0.55 | | | *0* | *0* | *0* | - | Melg607 | 1.39 | 1.35 | 1.34 | | | *0* | *0* | *0* | - | Melg19937 | 1.41 | 1.37 | 1.36 | | | *0* | *0* | *0* | - | Melg44497 | 1.42 | 1.35 | 1.37 | | | *0* | *0* | *0* | - | Mrg287 | 0.89 | 0.88 | 0.85 | | | *0* | *0* | *0* | - | Mrg1457 | 0.85 | 0.82 | 0.81 | | | *0* | *0* | *0* | - | Mrg49507 | 0.75 | 0.69 | 0.68 | | | *0* | *0* | *0* | - | Pcg64_32 | 0.56 | 0.52 | 0.49 | | | *0* | *0* | *0* | - | Pcg128_64 | 0.80 | 0.74 | 0.73 | | | *0* | *0* | *0* | - | Pcg1024_32 | 1.12 | 1.06 | 0.95 | | | *0* | *0* | *0* | - | Squares32 | 1.58 | 1.47 | 1.49 | | | *0* | *0* | *0* | - | Squares64 | 1.97 | 1.81 | 1.84 | | | *0* | *0* | *0* | - | Well512a | 2.80 | 2.74 | 2.43 | | | *n.a.* | *n.a.* | n.a. | - | Well1024a | 2.52 | 2.44 | 2.19 | | | *0* | *4* | *4* | - | Well19937c (1) | 3.48 | 3.44 | 3.06 | | | *0* | *2* | *2* | - | Well44497b | 3.96 | 3.91 | 3.40 | | | *n.a.* | *n.a.* | n.a. | - | Xiroshiro256 | 2.37 | 2.24 | 2.25 | | | *0* | *0* | *0* | - | Xiroshiro512 | 2.94 | 2.81 | 2.72 | | | *0* | *0* | *0* | - | Xiroshiro1024 | 2.78 | 2.59 | 2.41 | | | *0* | *0* | *0* | + | Cwg64 | 0.83 | 0.77 | 0.87 | 0.74 | | *0* | *0* | *0* | + | Cwg128_64 | 0.85 | 0.80 | 0.91 | 0.79 | | *0* | *0* | *0* | + | Cwg128 | 0.94 | 0.94 | 0.99 | 0.83 | | *0* | *0* | *0* | + | FastRand32 | 0.27 | 0.27 | 0.26 | 0.22 | | *11* | *106* | *too many* | + | FastRand63 | 0.30 | 0.29 | 0.29 | 0.24 | | *0* | *5* | *7* | + | LFib78 | 0.52 | 0.50 | 0.51 | 0.36 | | *0* | *0* | *0* | + | LFib116 | 0.53 | 0.52 | 0.51 | 0.38 | | *0* | *0* | *0* | + | LFib668 | 0.56 | 0.54 | 0.53 | 0.40 | | *0* | *0* | *0* | + | LFib1340 | 0.59 | 0.56 | 0.55 | 0.41 | | *0* | *0* | *0* | + | Melg607 | 1.39 | 1.35 | 1.34 | 1.08 | | *0* | *0* | *0* | + | Melg19937 | 1.41 | 1.37 | 1.36 | 1.20 | | *0* | *0* | *0* | + | Melg44497 | 1.42 | 1.35 | 1.37 | 1.23 | | *0* | *0* | *0* | + | Mrg287 | 0.89 | 0.88 | 0.85 | 0.61 | | *0* | *0* | *0* | + | Mrg1457 | 0.85 | 0.82 | 0.81 | 0.63 | | *0* | *0* | *0* | + | Mrg49507 | 0.75 | 0.69 | 0.68 | 0.57 | | *0* | *0* | *0* | + | Pcg64_32 | 0.56 | 0.52 | 0.49 | 0.43 | | *0* | *0* | *0* | + | Pcg128_64 | 0.80 | 0.74 | 0.73 | 0.67 | | *0* | *0* | *0* | + | Pcg1024_32 | 1.12 | 1.06 | 0.95 | 0.75 | | *0* | *0* | *0* | + | Squares32 | 1.58 | 1.47 | 1.49 | 1.39 | | *0* | *0* | *0* | + | Squares64 | 1.97 | 1.81 | 1.84 | 1.76 | | *0* | *0* | *0* | + | Well512a | 2.80 | 2.74 | 2.43 | 2.11 | | *n.a.* | *n.a.* | n.a. | + | Well1024a | 2.52 | 2.44 | 2.19 | 1.94 | | *0* | *4* | *4* | + | Well19937c (1) | 3.48 | 3.44 | 3.06 | 2.67 | | *0* | *2* | *2* | + | Well44497b | 3.96 | 3.91 | 3.40 | 3.09 | | *n.a.* | *n.a.* | n.a. | + | Xiroshiro256 | 2.37 | 2.24 | 2.25 | 1.95 | | *0* | *0* | *0* | + | Xiroshiro512 | 2.94 | 2.81 | 2.72 | 2.40 | | *0* | *0* | *0* | + | Xiroshiro1024 | 2.78 | 2.59 | 2.41 | 2.12 | | *0* | *0* | *0* | (1) The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with a *tempering* algorithm. (*missing values in empty columns are to come*) From 6ec95bc9711cb25cdba7353efcc6947519c7b6f6 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 12:51:29 +0100 Subject: [PATCH 09/14] #141-Py3.13 code review Completed. --- Python3.13/PyRandLib/__init__.py | 44 +++ Python3.13/PyRandLib/annotation_types.py | 33 ++ Python3.13/PyRandLib/basecwg.py | 116 +++++++ Python3.13/PyRandLib/baselcg.py | 108 ++++++ Python3.13/PyRandLib/baselfib64.py | 208 ++++++++++++ Python3.13/PyRandLib/basemelg.py | 202 +++++++++++ Python3.13/PyRandLib/basemrg.py | 186 ++++++++++ Python3.13/PyRandLib/basepcg.py | 121 +++++++ Python3.13/PyRandLib/baserandom.py | 412 +++++++++++++++++++++++ Python3.13/PyRandLib/basesquares.py | 165 +++++++++ Python3.13/PyRandLib/basewell.py | 291 ++++++++++++++++ Python3.13/PyRandLib/basexoroshiro.py | 191 +++++++++++ Python3.13/PyRandLib/cwg128.py | 162 +++++++++ Python3.13/PyRandLib/cwg128_64.py | 155 +++++++++ Python3.13/PyRandLib/cwg64.py | 155 +++++++++ Python3.13/PyRandLib/fastrand32.py | 125 +++++++ Python3.13/PyRandLib/fastrand63.py | 135 ++++++++ Python3.13/PyRandLib/lfib116.py | 135 ++++++++ Python3.13/PyRandLib/lfib1340.py | 137 ++++++++ Python3.13/PyRandLib/lfib668.py | 136 ++++++++ Python3.13/PyRandLib/lfib78.py | 135 ++++++++ Python3.13/PyRandLib/melg19937.py | 121 +++++++ Python3.13/PyRandLib/melg44497.py | 120 +++++++ Python3.13/PyRandLib/melg607.py | 120 +++++++ Python3.13/PyRandLib/mrg1457.py | 150 +++++++++ Python3.13/PyRandLib/mrg287.py | 151 +++++++++ Python3.13/PyRandLib/mrg49507.py | 141 ++++++++ Python3.13/PyRandLib/pcg1024_32.py | 284 ++++++++++++++++ Python3.13/PyRandLib/pcg128_64.py | 177 ++++++++++ Python3.13/PyRandLib/pcg64_32.py | 157 +++++++++ Python3.13/PyRandLib/splitmix.py | 144 ++++++++ Python3.13/PyRandLib/squares32.py | 114 +++++++ Python3.13/PyRandLib/squares64.py | 136 ++++++++ Python3.13/PyRandLib/well1024a.py | 139 ++++++++ Python3.13/PyRandLib/well19937c.py | 141 ++++++++ Python3.13/PyRandLib/well44497b.py | 141 ++++++++ Python3.13/PyRandLib/well512a.py | 140 ++++++++ Python3.13/PyRandLib/xoroshiro1024.py | 195 +++++++++++ Python3.13/PyRandLib/xoroshiro256.py | 186 ++++++++++ Python3.13/PyRandLib/xoroshiro512.py | 186 ++++++++++ Python3.13/testCPUPerfs.py | 81 +++++ Python3.13/testED.py | 142 ++++++++ 42 files changed, 6518 insertions(+) create mode 100644 Python3.13/PyRandLib/__init__.py create mode 100644 Python3.13/PyRandLib/annotation_types.py create mode 100644 Python3.13/PyRandLib/basecwg.py create mode 100644 Python3.13/PyRandLib/baselcg.py create mode 100644 Python3.13/PyRandLib/baselfib64.py create mode 100644 Python3.13/PyRandLib/basemelg.py create mode 100644 Python3.13/PyRandLib/basemrg.py create mode 100644 Python3.13/PyRandLib/basepcg.py create mode 100644 Python3.13/PyRandLib/baserandom.py create mode 100644 Python3.13/PyRandLib/basesquares.py create mode 100644 Python3.13/PyRandLib/basewell.py create mode 100644 Python3.13/PyRandLib/basexoroshiro.py create mode 100644 Python3.13/PyRandLib/cwg128.py create mode 100644 Python3.13/PyRandLib/cwg128_64.py create mode 100644 Python3.13/PyRandLib/cwg64.py create mode 100644 Python3.13/PyRandLib/fastrand32.py create mode 100644 Python3.13/PyRandLib/fastrand63.py create mode 100644 Python3.13/PyRandLib/lfib116.py create mode 100644 Python3.13/PyRandLib/lfib1340.py create mode 100644 Python3.13/PyRandLib/lfib668.py create mode 100644 Python3.13/PyRandLib/lfib78.py create mode 100644 Python3.13/PyRandLib/melg19937.py create mode 100644 Python3.13/PyRandLib/melg44497.py create mode 100644 Python3.13/PyRandLib/melg607.py create mode 100644 Python3.13/PyRandLib/mrg1457.py create mode 100644 Python3.13/PyRandLib/mrg287.py create mode 100644 Python3.13/PyRandLib/mrg49507.py create mode 100644 Python3.13/PyRandLib/pcg1024_32.py create mode 100644 Python3.13/PyRandLib/pcg128_64.py create mode 100644 Python3.13/PyRandLib/pcg64_32.py create mode 100644 Python3.13/PyRandLib/splitmix.py create mode 100644 Python3.13/PyRandLib/squares32.py create mode 100644 Python3.13/PyRandLib/squares64.py create mode 100644 Python3.13/PyRandLib/well1024a.py create mode 100644 Python3.13/PyRandLib/well19937c.py create mode 100644 Python3.13/PyRandLib/well44497b.py create mode 100644 Python3.13/PyRandLib/well512a.py create mode 100644 Python3.13/PyRandLib/xoroshiro1024.py create mode 100644 Python3.13/PyRandLib/xoroshiro256.py create mode 100644 Python3.13/PyRandLib/xoroshiro512.py create mode 100644 Python3.13/testCPUPerfs.py create mode 100644 Python3.13/testED.py diff --git a/Python3.13/PyRandLib/__init__.py b/Python3.13/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.13/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/Python3.13/PyRandLib/annotation_types.py b/Python3.13/PyRandLib/annotation_types.py new file mode 100644 index 0000000..cd7b252 --- /dev/null +++ b/Python3.13/PyRandLib/annotation_types.py @@ -0,0 +1,33 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +type Numerical = int | float +type StatesList = tuple[int] | list[int] +type StatesListAndExt = tuple[StatesList, int] +type StateType = StatesList | StatesListAndExt +type SeedStateType = Numerical | StateType + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.13/PyRandLib/basecwg.py b/Python3.13/PyRandLib/basecwg.py new file mode 100644 index 0000000..c437dd8 --- /dev/null +++ b/Python3.13/PyRandLib/basecwg.py @@ -0,0 +1,116 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.13/PyRandLib/baselcg.py b/Python3.13/PyRandLib/baselcg.py new file mode 100644 index 0000000..91e2d78 --- /dev/null +++ b/Python3.13/PyRandLib/baselcg.py @@ -0,0 +1,108 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.13/PyRandLib/baselfib64.py b/Python3.13/PyRandLib/baselfib64.py new file mode 100644 index 0000000..25ed4c8 --- /dev/null +++ b/Python3.13/PyRandLib/baselfib64.py @@ -0,0 +1,208 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.13/PyRandLib/basemelg.py b/Python3.13/PyRandLib/basemelg.py new file mode 100644 index 0000000..94ce4da --- /dev/null +++ b/Python3.13/PyRandLib/basemelg.py @@ -0,0 +1,202 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.13/PyRandLib/basemrg.py b/Python3.13/PyRandLib/basemrg.py new file mode 100644 index 0000000..c8e4ef3 --- /dev/null +++ b/Python3.13/PyRandLib/basemrg.py @@ -0,0 +1,186 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.13/PyRandLib/basepcg.py b/Python3.13/PyRandLib/basepcg.py new file mode 100644 index 0000000..1d8f17e --- /dev/null +++ b/Python3.13/PyRandLib/basepcg.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.13/PyRandLib/baserandom.py b/Python3.13/PyRandLib/baserandom.py new file mode 100644 index 0000000..4181af7 --- /dev/null +++ b/Python3.13/PyRandLib/baserandom.py @@ -0,0 +1,412 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random +from typing import override + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | binomialvariate**(self, n=1, p=0.5) + | Binomial distribution. Return the number of successes for n + | independent trials with the probability of success in each + | trial being p. + | n >= 0, 0.0 <= p <= 1.0, + | the result is an integer in the range 0 <= X <= n. + | + | + | expovariate(self, lambd=1.0) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + @override + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + @override + def getrandbits(self, k: int, /) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + @override + def randbytes(self, n: int, /) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: StateType, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + @override + def seed(self, _seed: SeedStateType = None, /) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Numerical | tuple[Numerical] | list[Numerical] = 1.0, + /, + times: int = 1 ) -> Numerical | list[Numerical]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64, /) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + hiMask = ((1 << _bitsCount) - 1) ^ (loMask := (1 << (_bitsCount - _rotCount)) - 1) + return ((_value & loMask) << _rotCount) | ((_value & hiMask) >> (_bitsCount - _rotCount)) + + +#===== end of module baserandom.py ===================================== diff --git a/Python3.13/PyRandLib/basesquares.py b/Python3.13/PyRandLib/basesquares.py new file mode 100644 index 0000000..0771c01 --- /dev/null +++ b/Python3.13/PyRandLib/basesquares.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.13/PyRandLib/basewell.py b/Python3.13/PyRandLib/basewell.py new file mode 100644 index 0000000..a89085e --- /dev/null +++ b/Python3.13/PyRandLib/basewell.py @@ -0,0 +1,291 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.13/PyRandLib/basexoroshiro.py b/Python3.13/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..a84d442 --- /dev/null +++ b/Python3.13/PyRandLib/basexoroshiro.py @@ -0,0 +1,191 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.13/PyRandLib/cwg128.py b/Python3.13/PyRandLib/cwg128.py new file mode 100644 index 0000000..080c3d3 --- /dev/null +++ b/Python3.13/PyRandLib/cwg128.py @@ -0,0 +1,162 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.13/PyRandLib/cwg128_64.py b/Python3.13/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..a4af759 --- /dev/null +++ b/Python3.13/PyRandLib/cwg128_64.py @@ -0,0 +1,155 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.13/PyRandLib/cwg64.py b/Python3.13/PyRandLib/cwg64.py new file mode 100644 index 0000000..3d2fb67 --- /dev/null +++ b/Python3.13/PyRandLib/cwg64.py @@ -0,0 +1,155 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int | float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.13/PyRandLib/fastrand32.py b/Python3.13/PyRandLib/fastrand32.py new file mode 100644 index 0000000..5a4bfac --- /dev/null +++ b/Python3.13/PyRandLib/fastrand32.py @@ -0,0 +1,125 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.13/PyRandLib/fastrand63.py b/Python3.13/PyRandLib/fastrand63.py new file mode 100644 index 0000000..60e7264 --- /dev/null +++ b/Python3.13/PyRandLib/fastrand63.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.13/PyRandLib/lfib116.py b/Python3.13/PyRandLib/lfib116.py new file mode 100644 index 0000000..238a277 --- /dev/null +++ b/Python3.13/PyRandLib/lfib116.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.13/PyRandLib/lfib1340.py b/Python3.13/PyRandLib/lfib1340.py new file mode 100644 index 0000000..9458baf --- /dev/null +++ b/Python3.13/PyRandLib/lfib1340.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.13/PyRandLib/lfib668.py b/Python3.13/PyRandLib/lfib668.py new file mode 100644 index 0000000..383c24a --- /dev/null +++ b/Python3.13/PyRandLib/lfib668.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.13/PyRandLib/lfib78.py b/Python3.13/PyRandLib/lfib78.py new file mode 100644 index 0000000..f505452 --- /dev/null +++ b/Python3.13/PyRandLib/lfib78.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.13/PyRandLib/melg19937.py b/Python3.13/PyRandLib/melg19937.py new file mode 100644 index 0000000..e061cae --- /dev/null +++ b/Python3.13/PyRandLib/melg19937.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.13/PyRandLib/melg44497.py b/Python3.13/PyRandLib/melg44497.py new file mode 100644 index 0000000..977f06e --- /dev/null +++ b/Python3.13/PyRandLib/melg44497.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.13/PyRandLib/melg607.py b/Python3.13/PyRandLib/melg607.py new file mode 100644 index 0000000..a34f222 --- /dev/null +++ b/Python3.13/PyRandLib/melg607.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.13/PyRandLib/mrg1457.py b/Python3.13/PyRandLib/mrg1457.py new file mode 100644 index 0000000..ab4c64c --- /dev/null +++ b/Python3.13/PyRandLib/mrg1457.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.13/PyRandLib/mrg287.py b/Python3.13/PyRandLib/mrg287.py new file mode 100644 index 0000000..0f19953 --- /dev/null +++ b/Python3.13/PyRandLib/mrg287.py @@ -0,0 +1,151 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.13/PyRandLib/mrg49507.py b/Python3.13/PyRandLib/mrg49507.py new file mode 100644 index 0000000..dc0cda6 --- /dev/null +++ b/Python3.13/PyRandLib/mrg49507.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.13/PyRandLib/pcg1024_32.py b/Python3.13/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..89dd52b --- /dev/null +++ b/Python3.13/PyRandLib/pcg1024_32.py @@ -0,0 +1,284 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState ) + + case 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + case _: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.13/PyRandLib/pcg128_64.py b/Python3.13/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..3507087 --- /dev/null +++ b/Python3.13/PyRandLib/pcg128_64.py @@ -0,0 +1,177 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.13/PyRandLib/pcg64_32.py b/Python3.13/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..5bf6e81 --- /dev/null +++ b/Python3.13/PyRandLib/pcg64_32.py @@ -0,0 +1,157 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.13/PyRandLib/splitmix.py b/Python3.13/PyRandLib/splitmix.py new file mode 100644 index 0000000..5e2853c --- /dev/null +++ b/Python3.13/PyRandLib/splitmix.py @@ -0,0 +1,144 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time +from typing import override + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + @override + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + @override + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.13/PyRandLib/squares32.py b/Python3.13/PyRandLib/squares32.py new file mode 100644 index 0000000..131b322 --- /dev/null +++ b/Python3.13/PyRandLib/squares32.py @@ -0,0 +1,114 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.13/PyRandLib/squares64.py b/Python3.13/PyRandLib/squares64.py new file mode 100644 index 0000000..66d15d7 --- /dev/null +++ b/Python3.13/PyRandLib/squares64.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.13/PyRandLib/well1024a.py b/Python3.13/PyRandLib/well1024a.py new file mode 100644 index 0000000..42140c1 --- /dev/null +++ b/Python3.13/PyRandLib/well1024a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.13/PyRandLib/well19937c.py b/Python3.13/PyRandLib/well19937c.py new file mode 100644 index 0000000..c5c5601 --- /dev/null +++ b/Python3.13/PyRandLib/well19937c.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 623, 622 + + case 1: + i_1, i_2 = 0, 623 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.13/PyRandLib/well44497b.py b/Python3.13/PyRandLib/well44497b.py new file mode 100644 index 0000000..ffd9ffb --- /dev/null +++ b/Python3.13/PyRandLib/well44497b.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 1390, 1389 + + case 1: + i_1, i_2 = 0, 1390 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.13/PyRandLib/well512a.py b/Python3.13/PyRandLib/well512a.py new file mode 100644 index 0000000..469c4e0 --- /dev/null +++ b/Python3.13/PyRandLib/well512a.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.13/PyRandLib/xoroshiro1024.py b/Python3.13/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..e69846e --- /dev/null +++ b/Python3.13/PyRandLib/xoroshiro1024.py @@ -0,0 +1,195 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + @override + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.13/PyRandLib/xoroshiro256.py b/Python3.13/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..e78af7c --- /dev/null +++ b/Python3.13/PyRandLib/xoroshiro256.py @@ -0,0 +1,186 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import override + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + @override + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.13/PyRandLib/xoroshiro512.py b/Python3.13/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..a8b65f0 --- /dev/null +++ b/Python3.13/PyRandLib/xoroshiro512.py @@ -0,0 +1,186 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, override + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + @override + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + @override + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + @override + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + @override + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.13/testCPUPerfs.py b/Python3.13/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.13/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.13/testED.py b/Python3.13/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.13/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= From f9594523a4366e1fd1ba0ea489e69436ce6cba45 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 12:53:30 +0100 Subject: [PATCH 10/14] #85-add Python 3.13 stuff in README.md Completed. --- README.md | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 506ca69..8c1b867 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,9 @@ We add in this table the evaluations provided by the authors of every new PRNGs | Well19937b (2) | WELL19937a | 624 x 4-bytes | 2^19,937 | 4.3 | 1.3 | 0 | 2 | 2 | | Well44497c | not available | 1,391 x 4-bytes | 2^44,497 | n.a. | n.a. | n.a. | n.a. | n.a. | | Mersenne twister | MT19937 | 624 x 4-bytes | 2^19,937 | 4.30 | 1.6 | 0 | 2 | 2 | - | Xiroshiro256 | *xiroshiro256*** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xiroshiro512 | *xiroshiro512*** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xiroshiro1024 | *xiroshiro1024*** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + | Xoroshiro256 | *xiroshiro256*** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | *xiroshiro512*** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | *xiroshiro1024*** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | (1) *or the generator original name in the related paper* (2) The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. @@ -121,33 +121,33 @@ The Python versions used for these evaluations in their related virtual environm **PyRandLib** time-64 bits table: | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | - | Cwg64 | 0.83 | 0.77 | 0.87 | 0.74 | | *0* | *0* | *0* | - | Cwg128_64 | 0.85 | 0.80 | 0.91 | 0.79 | | *0* | *0* | *0* | - | Cwg128 | 0.94 | 0.94 | 0.99 | 0.83 | | *0* | *0* | *0* | - | FastRand32 | 0.27 | 0.27 | 0.26 | 0.22 | | *11* | *106* | *too many* | - | FastRand63 | 0.30 | 0.29 | 0.29 | 0.24 | | *0* | *5* | *7* | - | LFib78 | 0.52 | 0.50 | 0.51 | 0.36 | | *0* | *0* | *0* | - | LFib116 | 0.53 | 0.52 | 0.51 | 0.38 | | *0* | *0* | *0* | - | LFib668 | 0.56 | 0.54 | 0.53 | 0.40 | | *0* | *0* | *0* | - | LFib1340 | 0.59 | 0.56 | 0.55 | 0.41 | | *0* | *0* | *0* | - | Melg607 | 1.39 | 1.35 | 1.34 | 1.08 | | *0* | *0* | *0* | - | Melg19937 | 1.41 | 1.37 | 1.36 | 1.20 | | *0* | *0* | *0* | - | Melg44497 | 1.42 | 1.35 | 1.37 | 1.23 | | *0* | *0* | *0* | - | Mrg287 | 0.89 | 0.88 | 0.85 | 0.61 | | *0* | *0* | *0* | - | Mrg1457 | 0.85 | 0.82 | 0.81 | 0.63 | | *0* | *0* | *0* | - | Mrg49507 | 0.75 | 0.69 | 0.68 | 0.57 | | *0* | *0* | *0* | - | Pcg64_32 | 0.56 | 0.52 | 0.49 | 0.43 | | *0* | *0* | *0* | - | Pcg128_64 | 0.80 | 0.74 | 0.73 | 0.67 | | *0* | *0* | *0* | - | Pcg1024_32 | 1.12 | 1.06 | 0.95 | 0.75 | | *0* | *0* | *0* | - | Squares32 | 1.58 | 1.47 | 1.49 | 1.39 | | *0* | *0* | *0* | - | Squares64 | 1.97 | 1.81 | 1.84 | 1.76 | | *0* | *0* | *0* | - | Well512a | 2.80 | 2.74 | 2.43 | 2.11 | | *n.a.* | *n.a.* | n.a. | - | Well1024a | 2.52 | 2.44 | 2.19 | 1.94 | | *0* | *4* | *4* | - | Well19937c (1) | 3.48 | 3.44 | 3.06 | 2.67 | | *0* | *2* | *2* | - | Well44497b | 3.96 | 3.91 | 3.40 | 3.09 | | *n.a.* | *n.a.* | n.a. | - | Xiroshiro256 | 2.37 | 2.24 | 2.25 | 1.95 | | *0* | *0* | *0* | - | Xiroshiro512 | 2.94 | 2.81 | 2.72 | 2.40 | | *0* | *0* | *0* | - | Xiroshiro1024 | 2.78 | 2.59 | 2.41 | 2.12 | | *0* | *0* | *0* | + | Cwg64 | 0.83 | 0.77 | 0.87 | 0.74 | 0.76 | *0* | *0* | *0* | + | Cwg128_64 | 0.85 | 0.80 | 0.91 | 0.79 | 0.79 | *0* | *0* | *0* | + | Cwg128 | 0.94 | 0.94 | 0.99 | 0.83 | 0.83 | *0* | *0* | *0* | + | FastRand32 | 0.27 | 0.27 | 0.26 | 0.22 | 0.22 | *11* | *106* | *too many* | + | FastRand63 | 0.30 | 0.29 | 0.29 | 0.24 | 0.22 | *0* | *5* | *7* | + | LFib78 | 0.52 | 0.50 | 0.51 | 0.36 | 0.35 | *0* | *0* | *0* | + | LFib116 | 0.53 | 0.52 | 0.51 | 0.38 | 0.36 | *0* | *0* | *0* | + | LFib668 | 0.56 | 0.54 | 0.53 | 0.40 | 0.39 | *0* | *0* | *0* | + | LFib1340 | 0.59 | 0.56 | 0.55 | 0.41 | 0.41 | *0* | *0* | *0* | + | Melg607 | 1.39 | 1.35 | 1.34 | 1.08 | 1.15 | *0* | *0* | *0* | + | Melg19937 | 1.41 | 1.37 | 1.36 | 1.20 | 1.23 | *0* | *0* | *0* | + | Melg44497 | 1.42 | 1.35 | 1.37 | 1.23 | 1.19 | *0* | *0* | *0* | + | Mrg287 | 0.89 | 0.88 | 0.85 | 0.61 | 0.61 | *0* | *0* | *0* | + | Mrg1457 | 0.85 | 0.82 | 0.81 | 0.63 | 0.61 | *0* | *0* | *0* | + | Mrg49507 | 0.75 | 0.69 | 0.68 | 0.57 | 0.56 | *0* | *0* | *0* | + | Pcg64_32 | 0.56 | 0.52 | 0.49 | 0.43 | 0.44 | *0* | *0* | *0* | + | Pcg128_64 | 0.80 | 0.74 | 0.73 | 0.67 | 0.63 | *0* | *0* | *0* | + | Pcg1024_32 | 1.12 | 1.06 | 0.95 | 0.75 | 0.75 | *0* | *0* | *0* | + | Squares32 | 1.58 | 1.47 | 1.49 | 1.39 | 1.37 | *0* | *0* | *0* | + | Squares64 | 1.97 | 1.81 | 1.84 | 1.76 | 1.67 | *0* | *0* | *0* | + | Well512a | 2.80 | 2.74 | 2.43 | 2.11 | 2.08 | *n.a.* | *n.a.* | n.a. | + | Well1024a | 2.52 | 2.44 | 2.19 | 1.94 | 1.87 | *0* | *4* | *4* | + | Well19937c (1) | 3.48 | 3.44 | 3.06 | 2.67 | 2.61 | *0* | *2* | *2* | + | Well44497b | 3.96 | 3.91 | 3.40 | 3.09 | 2.92 | *n.a.* | *n.a.* | n.a. | + | Xoroshiro256 | 2.37 | 2.24 | 2.25 | 1.95 | 1.93 | *0* | *0* | *0* | + | Xoroshiro512 | 2.94 | 2.81 | 2.72 | 2.40 | 2.30 | *0* | *0* | *0* | + | Xoroshiro1024 | 2.78 | 2.59 | 2.41 | 2.12 | 2.06 | *0* | *0* | *0* | (1) The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with a *tempering* algorithm. (*missing values in empty columns are to come*) From efdaf311490268aad3f844f38d4ba445885979ca Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 13:04:51 +0100 Subject: [PATCH 11/14] #145-remove former subdirectory PyRandLib Done. --- PyRandLib/__init__.py | 44 ---- PyRandLib/annotation_types.py | 35 --- PyRandLib/basecwg.py | 113 ---------- PyRandLib/baselcg.py | 104 --------- PyRandLib/baselfib64.py | 205 ----------------- PyRandLib/basemelg.py | 199 ----------------- PyRandLib/basemrg.py | 183 ---------------- PyRandLib/basepcg.py | 117 ---------- PyRandLib/baserandom.py | 401 ---------------------------------- PyRandLib/basesquares.py | 165 -------------- PyRandLib/basewell.py | 307 -------------------------- PyRandLib/basexoroshiro.py | 190 ---------------- PyRandLib/cwg128.py | 159 -------------- PyRandLib/cwg128_64.py | 153 ------------- PyRandLib/cwg64.py | 153 ------------- PyRandLib/fastrand32.py | 122 ----------- PyRandLib/fastrand63.py | 134 ------------ PyRandLib/lfib116.py | 133 ----------- PyRandLib/lfib1340.py | 134 ------------ PyRandLib/lfib668.py | 133 ----------- PyRandLib/lfib78.py | 132 ----------- PyRandLib/melg19937.py | 121 ---------- PyRandLib/melg44497.py | 120 ---------- PyRandLib/melg607.py | 120 ---------- PyRandLib/mrg1457.py | 148 ------------- PyRandLib/mrg287.py | 151 ------------- PyRandLib/mrg49507.py | 141 ------------ PyRandLib/pcg1024_32.py | 280 ------------------------ PyRandLib/pcg128_64.py | 176 --------------- PyRandLib/pcg64_32.py | 155 ------------- PyRandLib/splitmix.py | 138 ------------ PyRandLib/squares32.py | 110 ---------- PyRandLib/squares64.py | 132 ----------- PyRandLib/well1024a.py | 136 ------------ PyRandLib/well19937c.py | 135 ------------ PyRandLib/well44497b.py | 135 ------------ PyRandLib/well512a.py | 135 ------------ PyRandLib/xoroshiro1024.py | 204 ----------------- PyRandLib/xoroshiro256.py | 182 --------------- PyRandLib/xoroshiro512.py | 178 --------------- 40 files changed, 6213 deletions(-) delete mode 100644 PyRandLib/__init__.py delete mode 100644 PyRandLib/annotation_types.py delete mode 100644 PyRandLib/basecwg.py delete mode 100644 PyRandLib/baselcg.py delete mode 100644 PyRandLib/baselfib64.py delete mode 100644 PyRandLib/basemelg.py delete mode 100644 PyRandLib/basemrg.py delete mode 100644 PyRandLib/basepcg.py delete mode 100644 PyRandLib/baserandom.py delete mode 100644 PyRandLib/basesquares.py delete mode 100644 PyRandLib/basewell.py delete mode 100644 PyRandLib/basexoroshiro.py delete mode 100644 PyRandLib/cwg128.py delete mode 100644 PyRandLib/cwg128_64.py delete mode 100644 PyRandLib/cwg64.py delete mode 100644 PyRandLib/fastrand32.py delete mode 100644 PyRandLib/fastrand63.py delete mode 100644 PyRandLib/lfib116.py delete mode 100644 PyRandLib/lfib1340.py delete mode 100644 PyRandLib/lfib668.py delete mode 100644 PyRandLib/lfib78.py delete mode 100644 PyRandLib/melg19937.py delete mode 100644 PyRandLib/melg44497.py delete mode 100644 PyRandLib/melg607.py delete mode 100644 PyRandLib/mrg1457.py delete mode 100644 PyRandLib/mrg287.py delete mode 100644 PyRandLib/mrg49507.py delete mode 100644 PyRandLib/pcg1024_32.py delete mode 100644 PyRandLib/pcg128_64.py delete mode 100644 PyRandLib/pcg64_32.py delete mode 100644 PyRandLib/splitmix.py delete mode 100644 PyRandLib/squares32.py delete mode 100644 PyRandLib/squares64.py delete mode 100644 PyRandLib/well1024a.py delete mode 100644 PyRandLib/well19937c.py delete mode 100644 PyRandLib/well44497b.py delete mode 100644 PyRandLib/well512a.py delete mode 100644 PyRandLib/xoroshiro1024.py delete mode 100644 PyRandLib/xoroshiro256.py delete mode 100644 PyRandLib/xoroshiro512.py diff --git a/PyRandLib/__init__.py b/PyRandLib/__init__.py deleted file mode 100644 index bd75909..0000000 --- a/PyRandLib/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -This file is part of library PyRandLib. -It is provided under MIT License. -Please see files README.md and LICENSE. - -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com -""" - -from .basecwg import BaseCWG -from .baselcg import BaseLCG -from .baselfib64 import BaseLFib64 -from .basemelg import BaseMELG -from .basemrg import BaseMRG -from .baserandom import BaseRandom -from .basesquares import BaseSquares -from .basewell import BaseWELL -from .basexoroshiro import BaseXoroshiro -from .cwg64 import Cwg64 -from .cwg128_64 import Cwg128_64 -from .cwg128 import Cwg128 -from .fastrand32 import FastRand32 -from .fastrand63 import FastRand63 -from .lfib78 import LFib78 -from .lfib116 import LFib116 -from .lfib668 import LFib668 -from .lfib1340 import LFib1340 -from .melg607 import Melg607 -from .melg19937 import Melg19937 -from .melg44497 import Melg44497 -from .mrg287 import Mrg287 -from .mrg1457 import Mrg1457 -from .mrg49507 import Mrg49507 -from .pcg64_32 import Pcg64_32 -from .pcg128_64 import Pcg128_64 -from .pcg1024_32 import Pcg1024_32 -from .squares32 import Squares32 -from .squares64 import Squares64 -from .well512a import Well512a -from .well1024a import Well1024a -from .well19937c import Well19937c -from .well44497b import Well44497b -from .xoroshiro256 import Xoroshiro256 -from .xoroshiro512 import Xoroshiro512 -from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib/annotation_types.py b/PyRandLib/annotation_types.py deleted file mode 100644 index e097004..0000000 --- a/PyRandLib/annotation_types.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Tuple, Union - -Numerical = Union[ int, float ] -StatesList = Union[ Tuple[int], List[int] ] -StatesListAndExt = Tuple[ StatesList, int ] -StateType = Union[ StatesList, StatesListAndExt ] -SeedStateType = Union[ Numerical, StateType ] - - -#===== end of PyRandLib.annotation_types =============================== - -# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py deleted file mode 100644 index 40c2aa5..0000000 --- a/PyRandLib/basecwg.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesListAndExt - - -#============================================================================= -class BaseCWG( BaseRandom ): - """Definition of the base class for all Collatz-Weyl pseudo-random Generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - CWG models are chaotic generators that are combined with Weyl sequences to - eliminate the risk of short cycles. They have a large period, a uniform - distribution, and the ability to generate multiple independent streams by - changing their internal parameters (Weyl increment). CWGs owe their - exceptional quality to the arithmetical dynamics of noninvertible, - generalized, Collatz mappings based on the wellknown Collatz conjecture. - There is no jump function, but each odd number of the Weyl increment - initiates a new unique period, which enables quick initialization of - independent streams (this text is extracted from [8], see README.md). - - The internal implementation of the CWG algorithm varies according to its - implemented version. See implementation classes to get their formal - description. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with low computation time, medium period, 64- bits output values and very - good randomness characteristics. - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> StatesListAndExt: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For CWG, this state is defined by a list of control values - (a, weyl and s - or a list of 4 coeffs) and an internal state - value, which are used in methods 'next() and 'setstate() of - every inheriting class. - - All inheriting classes MUST IMPLEMENT this method. - """ - return (self._a, self._weyl, self._s, self._state) - - -#===== end of module basecwg.py ======================================== diff --git a/PyRandLib/baselcg.py b/PyRandLib/baselcg.py deleted file mode 100644 index acd6ce2..0000000 --- a/PyRandLib/baselcg.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical - - -#============================================================================= -class BaseLCG( BaseRandom ): - """Definition of the base class for all LCG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the evaluation - done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in - 'TestU01: A C Library for Empirical Testing of Random Number Generators - - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August - 2007'. It is not recommended to use such pseudo-random numbers generators - for serious simulation applications. - - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low - computation time but shorter period and worse randomness characteristics - than for FastRand63. - See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with - low computation time also, longer period and quite better randomness - characteristics than for FastRand32. - - Furthermore this class is callable: - rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> int: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For LCG, the state is defined with a single integer, 'self._state', - which has to be used in methods 'next() and 'setstate() of every - inheriting class. - """ - return self._state - -#===== end of module baselcg.py ======================================== diff --git a/PyRandLib/baselfib64.py b/PyRandLib/baselfib64.py deleted file mode 100644 index 96da67d..0000000 --- a/PyRandLib/baselfib64.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseLFib64( BaseRandom ): - """The base class for all LFib PRNG based on 64-bits numbers. - - Definition of the base class for all LFib pseudo-random generators based - on 64-bits generated numbers. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^(bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. - 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and - 2.4e+403 periods) while same computation time and far higher precision (64-bits - calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 - integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an - example. - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an - index in this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module baselfib64.py ===================================== - diff --git a/PyRandLib/basemelg.py b/PyRandLib/basemelg.py deleted file mode 100644 index e69665b..0000000 --- a/PyRandLib/basemelg.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseMELG( BaseRandom ): - """Definition of the base class for all MELG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: while the WELL algorithm use 32-bits integers as their internal state and - output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. This is the shortest period version proposed in paper [11]. - See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for - an example. - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE 64-bits integers, an index in this list and an - additional 64-bits integer as a state extension. Should _seedState - be a sole integer or float then it is used as initial seed for the - random filling of the internal state of the PRNG. Should _seedState - be anything else (e.g. None) then the shuffling of the local - current time value is used as such an initial seed. - - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE 64-bits integers, an index - in this list and an additional 64-bits integer as a state extension. - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - Should _seedstate not contain a list of self._STATE_SIZE 64- - bits integers, a value for attribute self._index and a value - for attribute self._extState, this method tries its best to - initialize all these values. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - elif count == 2: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: StateType = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module basemelg.py ======================================= diff --git a/PyRandLib/basemrg.py b/PyRandLib/basemrg.py deleted file mode 100644 index 8059c8e..0000000 --- a/PyRandLib/basemrg.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseMRG( BaseRandom ): - """Definition of the base class for all MRG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (32-bits modulus) but use of more memory space (1597 - integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. - See Mrg287 for an example. - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for the - random filling of the internal list of self._STATE_SIZE integers. - Should _seedState be anything else (e.g. None) then the shuffling - of the local current time value is used as such an initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in - this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] - - -#===== end of module basemrg.py ======================================== diff --git a/PyRandLib/basepcg.py b/PyRandLib/basepcg.py deleted file mode 100644 index 8c9a423..0000000 --- a/PyRandLib/basepcg.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical - - -#============================================================================= -class BasePCG( BaseRandom ): - """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low - computation time and medium period, with 2 32-bits word integers memory - consumption. Output values are returned on 32 bits. - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> int: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For LCG, the state is defined with a single integer, 'self._value', - which has to be used in methods 'random() and 'setstate() of every - inheriting class. - """ - return self._state - -#===== end of module basepcg.py ======================================== diff --git a/PyRandLib/baserandom.py b/PyRandLib/baserandom.py deleted file mode 100644 index 12b8598..0000000 --- a/PyRandLib/baserandom.py +++ /dev/null @@ -1,401 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from random import Random -from typing import List, Tuple, Union - -from .annotation_types import Numerical, SeedStateType, StateType - - -#============================================================================= -class BaseRandom( Random ): - """This is the base class for all pseudo-random numbers generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a - 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with - very low memory consumption (resp. 1 and 2 32-bits integers). - - See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib - generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, - 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher - precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and - 1279 32-bits integers). - - See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 32-bits integers memory consumption. - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (32-bits 47 integers). - See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low - computation time too (31-bits modulus) but use of more memory space (1597 - 32-bits integers). - - See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low - computation time, and for very low memory consumption for the two first (resp. 4, - 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, - i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump - ahead features. Since they all are exposing only a part of their internal state, - they are difficult to reverse and to predict. - - See Well512a, Well1024a, Well19937c and Well44479b for large to very large period - generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, - 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation - times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, - 624 and 1391 32-bits integers. - - Python built-in class random.Random is subclassed here to use a different basic - generator of our own devising: in that case, overriden methods are: - - random(), seed(), getstate(), and setstate(). - - Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method - next(). - - Furthermore this class and all its inheriting sub-classes are callable. Example: - rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Please notice that for simulating the roll of a dice you should program: - diceRoll = UFastRandom() - print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. - Such a programming is a simplified while still robust emulation of inherited - methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). - - Inheriting from random.Random, next methods are also available: - | - | betavariate(self, alpha, beta) - | Beta distribution. - | - | Conditions on the parameters are alpha > 0 and beta > 0. - | Returned values range between 0 and 1. - | - | - | choice(self, seq) - | Choose a random element from a non-empty sequence. - | - | - | expovariate(self, lambd) - | Exponential distribution. - | - | lambd is 1.0 divided by the desired mean. It should be - | nonzero. (The parameter would be called "lambda", but that is - | a reserved word in Python.) Returned values range from 0 to - | positive infinity if lambd is positive, and from negative - | infinity to 0 if lambd is negative. - | - | - | gammavariate(self, alpha, beta) - | Gamma distribution. Not the gamma function! - | - | Conditions on the parameters are alpha > 0 and beta > 0. - | - | - | gauss(self, mu, sigma) - | Gaussian distribution. - | - | mu is the mean, and sigma is the standard deviation. This is - | slightly faster than the normalvariate() function. - | - | Not thread-safe without a lock around calls. - | - | - | getrandbits(self, k) - | Returns a non-negative Python integer with k random bits. - | Changed since version 3.9: This method now accepts zero for k. - | - | - | getstate(self) - | Return internal state; can be passed to setstate() later. - | - | - | lognormvariate(self, mu, sigma) - | Log normal distribution. - | - | If you take the natural logarithm of this distribution, you'll get a - | normal distribution with mean mu and standard deviation sigma. - | mu can have any value, and sigma must be greater than zero. - | - | - | normalvariate(self, mu, sigma) - | Normal distribution. - | - | mu is the mean, and sigma is the standard deviation. - | - | - | paretovariate(self, alpha) - | Pareto distribution. alpha is the shape parameter. - | - | - | randbytes(self, n) - | Generate n random bytes. - | This method should not be used for generating security tokens. - | Notice: this method has been added in Python 3.9. It is implemented - | in PyRandLib for former versions of the language also. - | - | - | randint(self, a, b) - | Return random integer in range [a, b], including both end points. - | - | - | randrange(self, start, stop=None, step=1, int=) - | Choose a random item from range(start, stop[, step]). - | - | This fixes the problem with randint() which includes the - | endpoint; in Python this is usually not what you want. - | - | Do not supply the 'int' argument. - | - | - | sample(self, population, k) - | Chooses k unique random elements from a population sequence or set. - | - | Returns a new list containing elements from the population while - | leaving the original population unchanged. The resulting list is - | in selection order so that all sub-slices will also be valid random - | samples. This allows raffle winners (the sample) to be partitioned - | into grand prize and second place winners (the subslices). - | - | Members of the population need not be hashable or unique. If the - | population contains repeats, then each occurrence is a possible - | selection in the sample. - | - | To choose a sample in a range of integers, use range as an argument. - | This is especially fast and space efficient for sampling from a - | large population: sample(range(10000000), 60) - | - | - | seed(self, a=None, version=2) - | Initialize internal state from hashable object. - | - | None or no argument seeds from current time or from an operating - | system specific randomness source if available. - | - | For version 2 (the default), all of the bits are used if *a *is a str, - | bytes, or bytearray. For version 1, the hash() of *a* is used instead. - | - | If *a* is an int, all bits are used. - | - | - | setstate(self, state) - | Restore internal state from object returned by getstate(). - | - | - | shuffle(self, x, random=None, int=) - | x, random=random.random -> shuffle list x in place; return None. - | - | Optional arg random is a 0-argument function returning a random - | float in [0.0, 1.0); by default, the standard random.random. - | - | - | triangular(self, low=0.0, high=1.0, mode=None) - | Triangular distribution. - | - | Continuous distribution bounded by given lower and upper limits, - | and having a given mode value in-between. - | - | http://en.wikipedia.org/wiki/Triangular_distribution - | - | - | uniform(self, a, b) - | Get a random number in the range [a, b) or [a, b] depending on rounding. - | - | - | vonmisesvariate(self, mu, kappa) - | Circular data distribution. - | - | mu is the mean angle, expressed in radians between 0 and 2*pi, and - | kappa is the concentration parameter, which must be greater than or - | equal to zero. If kappa is equal to zero, this distribution reduces - | to a uniform random angle over the range 0 to 2*pi. - | - | - | weibullvariate(self, alpha, beta) - | Weibull distribution. - | - | alpha is the scale parameter and beta is the shape parameter. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 32 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seed: SeedStateType = None) -> None: - """Constructor. - - Should _seed be None or not a number then the local time is used - (with its shuffled value) as a seed. - - Notice: the Python built-in base class random.Random internally - calls method setstate() which MUST be overridden in classes that - inherit from class BaseRandom. - """ - super().__init__( _seed ) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - - This is the core of the PRNGs. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def random(self) -> float: - """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). - - Inheriting classes MUST OVERRIDE the value of the base class constant - attribute _NORMALIZE when the random integer values they return are - coded on anything else than 32 bits. - """ - return self.next() * self._NORMALIZE - - - #------------------------------------------------------------------------- - def getrandbits(self, k: int) -> int: - """Returns k bits from the internal state of the generator. - - k must be a positive value greater or equal to zero. - """ - assert k >= 0, "the returned bits count must not be negative" - assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" - - return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) - - - #------------------------------------------------------------------------- - def randbytes(self, n: int) -> bytes: - """Generates n random bytes. - - This method should not be used for generating security tokens. - (use Python built-in secrets.token_bytes() instead) - """ - assert n >= 0 # and self._OUT_BITS >= 8 - return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can then be passed to setstate() to restore the state. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def setstate(self, _state: StateType) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call to getstate(), - and setstate() restores the internal state of the generator to what - it was at the time setstate() was called. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def seed(self, _seed: SeedStateType = None) -> None: - """Initiates the internal state of this pseudo-random generator. - """ - try: - self.setstate( _seed ) - except: - super().seed( _seed ) - - - #------------------------------------------------------------------------- - def __call__(self, _max : Union[Numerical, - Tuple[Numerical], - List[Numerical]] = 1.0, - times: int = 1 ) -> Union[Numerical, List[Numerical]]: - """This class's instances are callable. - - The returned value is uniformly contained within the - interval [0.0 : _max]. When times is set, a list of - iterated pseudo-random values is returned. 'times' - must be an integer. If less than 1 it is forced to - be 1. - '_max' may be a list or a tuple of values, in which - case a list of related pseudo-random values is - returned with entries of the same type than the same - indexed entry in '_max'. - """ - assert isinstance( times, int ) - if times < 1: - times = 1 - - if isinstance( _max, int ): - ret = [ int(_max * self.random()) for _ in range(times) ] - elif isinstance( _max, float ): - ret = [ _max * self.random() for _ in range(times) ] - else: - try: - if times == 1: - ret = [ self(m,1) for m in _max ] - else: - ret = [ [self(m,1) for m in _max] for _ in range(times) ] - except: - ret = [ self.__call__(times=1) ] - - return ret[0] if len(ret) == 1 else ret - - - #------------------------------------------------------------------------- - @classmethod - def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: - """Returns the value of a left rotating by _rotCount bits - - Useful for some inheriting classes. - """ - #assert 1 <=_rotCount <= _bitsCount - loMask = (1 << (_bitsCount - _rotCount)) - 1 - hiMask = ((1 << _bitsCount) - 1) ^ loMask - hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) - return ((_value & loMask) << _rotCount) | hiBits - - -#===== end of module baserandom.py ===================================== diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py deleted file mode 100644 index 2968237..0000000 --- a/PyRandLib/basesquares.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesList -from .splitmix import SplitMix32 - - -#============================================================================= -class BaseSquares( BaseRandom ): - """Definition of the base class for the Squares counter-based pseudo-random Generator. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Squares models are based on an incremented counter and a key. The - algorithm squares a combination of the counter and the key values, - and exchanges the upper and lower bits of the combination, the - whole repeated a number of times (4 to 5 rounds). Output values - are provided on 32-bits or on 64-bits according to the model. See - [9] in README.md. - - See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 32-bits output values and - very good randomness characteristics. - - See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 64-bits output values and - very good randomness characteristics. Caution: the 64-bits version - should not pass the birthday test, which is a randmoness issue, - while this is not mentionned in the original paper (see [9] in - file README.md). - - Furthermore this class is callable: - rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> StatesList: - """Returns an object capturing the current internal state of the generator. - """ - return (self._counter, self._key) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType) -> None: - """Restores or sets the internal state of the generator. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._counter = 0 - self._key = self._initKey( _state ) - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - self._counter = 0 - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) - else: - self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) - - else: - try: - self._counter = _state[0] & 0xffff_ffff_ffff_ffff - self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd - except: - # uses local time as initial seed - self._counter = 0 - self._key = self._initKey() - - - #------------------------------------------------------------------------- - def _initKey(self, _seed: int = None) -> int: - """Initalizes the attribute _key according to the original recommendations - see [9]. - """ - hexDigits = [ i for i in range(1, 16) ] - key = 0 - - initRand = SplitMix32( _seed ) - - # 8 high hexa digits - all different - n = 15 - while n >= 8: - k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class - h = hexDigits[ k ] - key <<= 4 - key += h - n -= 1 - if k < n: - hexDigits[ k ] = hexDigits[ n ] - hexDigits[ n ] = h - - # 8 low hexa digits - all different - n = 15 - while n >= 8: - k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class - h = hexDigits[ k ] - key <<= 4 - key += h - n -= 1 - if k < n: - hexDigits[ k ] = hexDigits[ n ] - hexDigits[ n ] = h - - return key | 1 # Notice: key must be odd - - -#===== end of module basesquares.py ==================================== diff --git a/PyRandLib/basewell.py b/PyRandLib/basewell.py deleted file mode 100644 index b7dc2ac..0000000 --- a/PyRandLib/basewell.py +++ /dev/null @@ -1,307 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StateType -from .splitmix import SplitMix32 - - -#============================================================================= -class BaseWELL( BaseRandom ): - """Definition of the base class for all WELL pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in the 4 different versions implemented here has been coded - here as a direct implementation of their descriptions in the initial paper - "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François - PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO - (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, - No. 1, March 2006, Pages 1–16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - So, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory little consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for - an example. - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937b generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for the - random filling of the internal list of self._STATE_SIZE integers. - Should _seedState be anything else (e.g. None) then the shuffling - of the local current time value is used as such an initial seed. - - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in - this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: StateType = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix32( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - - #------------------------------------------------------------------------- - @classmethod - def _M0(cls, x: int = None) -> int: - return 0 - - #------------------------------------------------------------------------- - @classmethod - def _M1(cls, x: int) -> int: - return x - - #------------------------------------------------------------------------- - @classmethod - def _M2_pos(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x >> t - - #------------------------------------------------------------------------- - @classmethod - def _M2_neg(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return (x << t) & 0xffff_ffff - - #------------------------------------------------------------------------- - @classmethod - def _M3_pos(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x ^ (x >> t) - - #------------------------------------------------------------------------- - @classmethod - def _M3_neg(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x ^ ((x << t) & 0xffff_ffff) - - #------------------------------------------------------------------------- - @classmethod - def _M4(cls, x: int, a: int) -> int: - #assert 0 <= a <= 0xffff_ffff - return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 - - #------------------------------------------------------------------------- - @classmethod - def _M5_pos(cls, x: int, t: int, a: int) -> int: - #assert 0 <= t < 32 - #assert 0 <= b <= 0xffff_ffff - return x ^ ((x >> t) & a) - - #------------------------------------------------------------------------- - @classmethod - def _M5_neg(cls, x: int, t: int, a: int) -> int: - #assert 0 <= t < 32 - #assert 0 <= a <= 0xffff_ffff - return x ^ ((x << t) & a) - - #------------------------------------------------------------------------- - @classmethod - def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: - #assert 0 <= q < 32 - #assert 0 <= t < 32 - #assert 0 <= s < 32 - #assert 0 <= a <= 0xffff_ffff - y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) - return y ^ a if x & (1 << t) else y - - #------------------------------------------------------------------------- - @classmethod - def _d(cls, s: int) -> int: - #assert 0 <= s < 32 - return 0xffff_ffff ^ (1 << s) - - #------------------------------------------------------------------------- - @classmethod - def _tempering(cls, x: int, b: int, c: int) -> int: - #assert 0 <= b <= 0xffff_ffff - #assert 0 <= c <= 0xffff_ffff - #assert 0 <= w <= 32 - # notice: the generic algorithm truncs x on w-bits. All of the implemented - # ones in PyRandLib are set on 32-bits. So, no truncation takes place here - x = x ^ (((x << 7) & 0xffff_ffff) & b) - return x ^ (((x << 15) & 0xffff_ffff) & c) - - #------------------------------------------------------------------------- - @property - def _a1(self): - return 0xda44_2d24 - - @property - def _a2(self): - return 0xd3e4_3ffd - - @property - def _a3(self): - return 0x8bdc_b91e - - @property - def _a4(self): - return 0x86a9_d87e - - @property - def _a5(self): - return 0xa8c2_96d1 - - @property - def _a6(self): - return 0x5d6b_45cc - - @property - def _a7(self): - return 0xb729_fcec - -#===== end of module basewell.py ======================================= diff --git a/PyRandLib/basexoroshiro.py b/PyRandLib/basexoroshiro.py deleted file mode 100644 index 1226391..0000000 --- a/PyRandLib/basexoroshiro.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Union - -from .baserandom import BaseRandom -from .annotation_types import Numerical, StatesList, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseXoroshiro( BaseRandom ): - """The base class for all xoroshiro PRNGs. - - Definitiion of the base class for all versions of the xoroshiro algorithm - implemented in PyRandLib. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number - Generators. The xoroshiro linear transformation updates cyclically two words of a - larger state array. The base xoroshiro linear transformation is obtained combining - a rotation, a shift, and again a rotation. - (extracted from the original paper, see [10] in file README.md) - - An addition or a multiplication operation is internally applied also to the state - of the PRNGs. Doubling the same operation has proven to enhance then randomness - quality of the PRNG. This is the model of the algorithms that is implemeted in - PyRandLib. - - The implemented algorithms shortly escape from the zeroland (10 to 100 calls are - enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 - version of the algorithm has nevertheless shown close repeats flaws, with a bad - Hamming weight near zero. Xoroshiro512 seems to best fit this property. - (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). - - See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. - 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 - periods), 64-bits precision calculations and short memory consumption (resp. 8, - 16 and 32 integers coded on 64 bits. - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 - for an example. - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO: int = (1 << 64) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> List[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return self._state[:] - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module basexoroshiro.py ================================== - diff --git a/PyRandLib/cwg128.py b/PyRandLib/cwg128.py deleted file mode 100644 index 22fbab2..0000000 --- a/PyRandLib/cwg128.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg128( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 128-bits calculations and 128-bits output values with large - period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG - algorithms offer multi streams features, by simply using different initial - settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 96 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - Notice: in the original paper, four control value c[0] to c[3] are used. - It appears that these value are used just are 's' for c[0], 'x' for c[1], - 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg128() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 128 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO: int = (1 << 128) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & self._MODULO - self._weyl = (self._weyl + self._s) & self._MODULO - self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO - # returns the xored-shifted output value - return self._state ^ (self._a >> 96) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits - self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd - - else: - try: - self._a = _state[0] & self._MODULO - self._weyl = _state[1] & self._MODULO - self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd - self._state = _state[3] & self._MODULO - - except: - # uses local time as initial seed - self.setstate() - -#===== end of module cwg128.py ========================================= diff --git a/PyRandLib/cwg128_64.py b/PyRandLib/cwg128_64.py deleted file mode 100644 index 5f7a936..0000000 --- a/PyRandLib/cwg128_64.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg128_64( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 128-bits calculations and 64-bits output values with small - period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG - algorithms offer multi streams features, by simply using different - initial settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 48 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg128_64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff - self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff - self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff - # returns the xored-shifted output value - return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._s = initRand() | 1; # Notice: must be odd - self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits - - else: - try: - self._a = _state[0] & 0xffff_ffff_ffff_ffff - self._weyl = _state[1] & 0xffff_ffff_ffff_ffff - self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd - self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits - - except: - # uses local time as initial seed - self.setstate() - - -#===== end of module cwg128_64.py ====================================== diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py deleted file mode 100644 index 7b34c77..0000000 --- a/PyRandLib/cwg64.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg64( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 64-bits calculations and 64-bits output values with small - period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG - algorithms offer multi streams features, by simply using different - initial settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 48 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff - self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff - self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff - # returns the xored-shifted output value - return self._state ^ (self._a >> 48) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._s = initRand() | 1; # Notice: must be odd - self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits - - else: - try: - self._a = _state[0] & 0xffff_ffff_ffff_ffff - self._weyl = _state[1] & 0xffff_ffff_ffff_ffff - self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd - self._state = _state[3] & 0xffff_ffff_ffff_ffff - - except: - # uses local time as initial seed - self.setstate() - - -#===== end of module cwg64.py ========================================== diff --git a/PyRandLib/fastrand32.py b/PyRandLib/fastrand32.py deleted file mode 100644 index 9813c66..0000000 --- a/PyRandLib/fastrand32.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselcg import BaseLCG -from .annotation_types import Numerical -from .splitmix import SplitMix32 - - -#============================================================================= -class FastRand32( BaseLCG ): - """ - Pseudo-random numbers generator - Linear Congruential Generator dedicated - to 32-bits calculations with very short period (about 4.3e+09) but very - short time computation. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. - - The implementation of this LCG 32-bits model is based on (a=69069, c=1) - since these two values have evaluated to be the 'best' ones for LCGs - within TestU01 while m = 2^32. - - See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with - low computation time also, longer period and quite better randomness - characteristics than for FastRand32. - - Furthermore this class is callable: - rand = FastRand32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = FastRand32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff - return self._state - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix32( _state ) - self._state = initRand() - else: - initRand = SplitMix32() - self._state = initRand() - -#===== end of module fastrand32.py ===================================== diff --git a/PyRandLib/fastrand63.py b/PyRandLib/fastrand63.py deleted file mode 100644 index 4483c40..0000000 --- a/PyRandLib/fastrand63.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselcg import BaseLCG -from .annotation_types import Numerical -from .splitmix import SplitMix63 - - -#============================================================================= -class FastRand63( BaseLCG ): - """ - Pseudo-random numbers generator - Linear Congruential Generator dedicated - to 63-bits calculations with very short period (about 9.2e+18) and short - time computation. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. - - The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) - since these two values have evaluated to be the 'best' ones for LCGs - within TestU01 while m = 2^63. - - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low - computation time but shorter period and worse randomness characteristics - than for FastRand63. - - Furthermore this class is callable: - rand = FastRand63() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = FastRand63() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 63 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff - return self._state - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix63( _state ) - self._state = initRand() - else: - initRand = SplitMix63() - self._state = initRand() - - -#===== end of module fastrand63.py ===================================== diff --git a/PyRandLib/lfib116.py b/PyRandLib/lfib116.py deleted file mode 100644 index 8f1f000..0000000 --- a/PyRandLib/lfib116.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib116( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (8.3e+34). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = (x(i-24) + x(i-55)) mod 2^64 - - and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due - to the use of a 2^64 modulo and little memory space consumption (55 long integ- - ers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, - 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 17, 607 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib116() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib116() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-5 and i-17 -th values - k24 = self._index-24 - if k24 < 0: - k24 += LFib116._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib116._STATE_SIZE - - return myValue - -#===== end of module lfib116.py ======================================== diff --git a/PyRandLib/lfib1340.py b/PyRandLib/lfib1340.py deleted file mode 100644 index 5007045..0000000 --- a/PyRandLib/lfib1340.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib1340( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (2.4e+403). - - This module is part of library PyRandLib. - - Copyright (c) 2017-2025 Philippe Schmouker - - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = (x(i-861) + x(i-1279)) mod 2^64 - - and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time - due to the use of a 2^64 modulo but memory space consumption (1379 long integ- - ers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, - 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while - same computation time and far higher precision (64-bits calculations) than MRGs, - but memory consumption (resp. 17, 55 and 607 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib1340() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib1340() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-861 and i-1279 -th values - k861 = self._index-861 - if k861 < 0: - k861 += LFib1340._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib1340._STATE_SIZE - - return myValue - -#===== end of module lfib1340.py ====================================== diff --git a/PyRandLib/lfib668.py b/PyRandLib/lfib668.py deleted file mode 100644 index 0f5e494..0000000 --- a/PyRandLib/lfib668.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib668( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (1.2e+201). - - This module is part of library PyRandLib. - - Copyright (c) 2017-2025 Philippe Schmouker - - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = ( x(i-273) + x(i-607) ) mod 2^64 - - and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due - to the use of a 2^64 modulo but memory space consumption (607 long integers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, - 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 17, 55 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib668() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib668() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-273 and i-607 -th values - k273 = self._index-273 - if k273 < 0: - k273 += LFib668._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib668._STATE_SIZE - - return myValue - -#===== end of module lfib668.py ======================================= diff --git a/PyRandLib/lfib78.py b/PyRandLib/lfib78.py deleted file mode 100644 index 7f3589c..0000000 --- a/PyRandLib/lfib78.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib78( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (3.0e+23). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = ( x(i-5) + x(i-17) ) mod 2^64 - - and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due - to the use of a 2^64 modulo and few memory space consumption (17 long integers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, - 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 55, 607 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib78() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib78() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-5 and i-17 -th values - k5 = self._index-5 - if k5 < 0: - k5 += LFib78._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib78._STATE_SIZE - - return myValue - -#===== end of module lfib78.py ========================================= diff --git a/PyRandLib/melg19937.py b/PyRandLib/melg19937.py deleted file mode 100644 index 5d9b093..0000000 --- a/PyRandLib/melg19937.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg19937( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Furthermore, this class is callable: - rand = Melg19937() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg19937() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 312 - _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 311 - self._index = i_1 - - s311 = self._state[311] - - x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) - self._state[311] = s311 - - si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) - return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg44497.py b/PyRandLib/melg44497.py deleted file mode 100644 index 286cdd7..0000000 --- a/PyRandLib/melg44497.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg44497( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. - See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - - Furthermore, this class is callable: - rand = Melg444907() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg444907() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 696 - _A_COND = (0, 0x4fa9_ca36_f293_c9a9) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 695 - self._index = i_1 - - s695 = self._state[695] - - x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) - self._state[695] = s695 - - si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) - return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg607.py b/PyRandLib/melg607.py deleted file mode 100644 index 6a38a55..0000000 --- a/PyRandLib/melg607.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg607( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Furthermore, this class is callable: - rand = Melg607() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg607() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 - _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 9 - self._index = i_1 - - s9 = self._state[9] - - x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) # M=5, s1=13 - self._state[9] = s9 - - si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) # s2=35 - return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) # s3=30, L=3, b = 0x66ed... - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib/mrg1457.py b/PyRandLib/mrg1457.py deleted file mode 100644 index 17f8c32..0000000 --- a/PyRandLib/mrg1457.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg1457( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with long period (3.98e+438). - - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random - generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence - - x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation - time. - - See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (31-bits modulus) but use of more memory space (1597 - integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg1457() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers - _MODULO : int = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 31 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # The DX-47-3 version uses the recurrence - # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values - k1 = self._index-1 - if k1 < 0: - k1 = Mrg1457._STATE_SIZE - 1 - - k24 = self._index-24 - if k24 < 0: - k24 += Mrg1457._STATE_SIZE - - # then evaluates current value - myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 - self._state[self._index] = myValue - - # next index - self._index = (self._index + 1) % Mrg1457._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib/mrg287.py b/PyRandLib/mrg287.py deleted file mode 100644 index 119c3f0..0000000 --- a/PyRandLib/mrg287.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg287( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive - Generator with a long period (2.49e+86). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 32-bits model is based on a Lagged Fibonacci - generator (LFIB), the Marsa-LFIB4 one. - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) paper. - - The Marsa-LIBF4 version uses the recurrence - - x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due - to the use of a 2^32 modulo. - - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (31-bits modulus) but use of more memory space (1_597 - integers). - - Furthermore, this class is callable: - rand = Mrg287() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg287() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO : int = 0xffff_ffff - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # The Marsa-LIBF4 version uses the recurrence - # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values - k55 = self._index-55 - if k55 < 0: - k55 += Mrg287._STATE_SIZE - - k119 = self._index-119 - if k119 < 0: - k119 += Mrg287._STATE_SIZE - - k179 = self._index-179 - if k179 < 0: - k179 += Mrg287._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % self._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib/mrg49507.py b/PyRandLib/mrg49507.py deleted file mode 100644 index 2eb2597..0000000 --- a/PyRandLib/mrg49507.py +++ /dev/null @@ -1,141 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg49507( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with a very long period (1.17e+14_903). - - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It - uses the recurrence - - x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) - - and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low - computation time. - - See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - rand = Mrg49507() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg49507() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers - _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 31 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-7, i-1597 -th values - k7 = self._index-7 - if k7 < 0: - k7 += Mrg49507._STATE_SIZE - - # then evaluates current value - myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % Mrg49507._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand49507.py =================================== diff --git a/PyRandLib/pcg1024_32.py b/PyRandLib/pcg1024_32.py deleted file mode 100644 index c721499..0000000 --- a/PyRandLib/pcg1024_32.py +++ /dev/null @@ -1,280 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .annotation_types import Numerical, SeedStateType, StateType -from .pcg64_32 import Pcg64_32 -from .splitmix import SplitMix32 - - -#============================================================================= -class Pcg1024_32( Pcg64_32 ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - extended by 1,024 equistributed generators, dedicated to 64-bits - calculations and 32-bits output with very large period (about 6.53e+9882) - but very short time computation. This version of the PCG algorithm gets - the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers - jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version - of th e PCG algorithm, as specified in the related paper (see [7] in - document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 - and the additional permutation output function and its internal multiple - states that implements its 1024-dimensionally equidistributed generator - directly coded in method 'next()'. - - See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low - computation time also and a longer period than for Pcg64_32, with 2 - 32-bits word integers memory consumption. Output values are returned on - 32 bits. - - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg1024_32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg1024_32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - _EXTENDED_STATE_SIZE: int = 1024 - - - #------------------------------------------------------------------------- - def __init__(self, _seed: SeedStateType = None) -> None: - """Constructor. - - Should _seed be None or not be of SeedStateType then the - local time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates a to-be-xor'ed 32-bits value from current extended state - if self._state & 0xffff_ffff == 0: - self._advancetable() - extendedValue = self._extendedState[ self._state & 0x03ff ] - - # then xor's it with the next 32-bits value evaluated with the internal state - return super().next() ^ extendedValue - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a list that contains self._STATE_SIZE integers. - """ - return [ self._extendedState[:], self._state ] - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: SeedStateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState ) - - elif count == 2: - # each entry in _seedState[0] MUST be a 32-bits integer - # _seedState[1] MUST be a 64-bits integer - extendedCount = len( _seedState[0] ) - if extendedCount == self._EXTENDED_STATE_SIZE: - self._extendedState = _seedState[0][:] - self._state = _seedState[1] - elif extendedCount > 0: - extended = _seedState[0] - for s in _seedState[1:]: - extended ^= s - self._initextendedstate( extended ) - self._state = _seedState[1] - else: - self._initstate( _seedState[1] ) - - else: - self._initstate() - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _advancetable(self) -> None: - """Advances the extended states - """ - carry = False - for i, s in enumerate( self._extendedState ): - if carry: - carry = self._extendedstep(s, i) - if self._extendedstep(s, i): # notice: must be evaluated before carry is set - carry = True - - - #------------------------------------------------------------------------- - def _extendedstep(self, value: int, i: int) -> bool: - """Evaluates new extended state indexed value in the extended state table. - - Returns True when the evaluated extended value is set to zero on all bits - but its two lowest ones - these two bits never change with MCGs. - """ - state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff - state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) - state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff - - result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) - result ^= result >> 22 - - self._extendedState[i] = result - - return result == state & 0b11 - - - #------------------------------------------------------------------------- - def _initextendedstate(self, _initialSeed: Numerical = None) -> None: - """Inits the extended list of values. - - Inits the extended list of values according to some initial - seed that has to be an integer, or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed. - initRand = SplitMix32( _initialSeed ) - self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal state of this PRNG. - - Inits its current state and its extended state also. The - initial seed has to be an integer, or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as the initial seed value. - The same initial seed is finally used to seed the current - state and the extended state. To avoid any unexpected - correlation between current state and any value of the - extended one, we use different PRNGs to seed the internal - state on one side and the extended state on the other side. - """ - super().setstate( _initialSeed ) # uses Pcg64_32() - self._initextendedstate( _initialSeed ) # uses Well1024a() - - - #------------------------------------------------------------------------- - @classmethod - def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: - """Evaluates the inversion of an xor-shift operation. - """ - if shift * 2 >= bitsCount: - return value ^ (value >> shift) - - botMask = (1 << (bitsCount - shift * 2)) - 1 - topMask = ~botMask & 0xffff_ffff - - top = (value ^ (value >> shift)) & topMask - - newBitsShift = bitsCount - shift - bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask - - return top | bot - - -#===== end of module pcg1024_32.py ===================================== diff --git a/PyRandLib/pcg128_64.py b/PyRandLib/pcg128_64.py deleted file mode 100644 index 6f9eb84..0000000 --- a/PyRandLib/pcg128_64.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basepcg import BasePCG -from .annotation_types import Numerical -from .splitmix import SplitMix64 - - -#============================================================================= -class Pcg128_64( BasePCG ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - dedicated to 128-bits calculations and 64-bits output with medium period - (about 3.40e+38) but very short time computation. The PCG algorithm - offers jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of - the PCG algorithm, as specified in the related paper (see [7] in document - README.md), so with a and c values coded on 128 bits (see method next()), - the modulo m = 2^128 and the additional permutation output function - directly implemented in method 'next()'. - - See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low - computation time also and a longer period than for Pcg64_32, with 2 - 32-bits word integers memory consumption. Output values are returned on - 32 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg128_64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg128_64() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO_128 : int = (1 << 128) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - current_state = self._state - self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & self._MODULO_128 - # the permutated output is then computed - random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state - current_state ^= current_state >> 64 # fixed shift XOR is then evaluated - value = current_state & 0xffff_ffff_ffff_ffff - rot_mask = (1 << random_rotation) - 1 - return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._state = _state & self._MODULO_128 - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._state = int( _state + 0.5 ) & self._MODULO_128 - else: - self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 - - else: - # uses local time as initial seed - initRand = SplitMix64() - self._state = (initRand() << 64) | initRand() - -#===== end of module pcg128_64.py ====================================== diff --git a/PyRandLib/pcg64_32.py b/PyRandLib/pcg64_32.py deleted file mode 100644 index 1fa867e..0000000 --- a/PyRandLib/pcg64_32.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basepcg import BasePCG -from .annotation_types import Numerical -from .splitmix import SplitMix64 - - -#============================================================================= -class Pcg64_32( BasePCG ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - dedicated to 64-bits calculations and 32-bits output with medium period - (about 1.84e+19) but very short time computation. The PCG algorithm - offers jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the - PCG algorithm, as specified in the related paper (see [7] in document - README.md), so with a = 6364136223846793005, c = 1442695040888963407, the - modulo m = 2^64 and the additional permutation output function directly - implemented in method 'next()'. - - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg64_32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg64_32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - current_state = self._state - self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff - # the permutated output is then computed - random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state - current_state ^= current_state >> 22 # fixed shift XOR is then evaluated - return (current_state >> (22 + random_shift)) & 0xffff_ffff - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._state = _state & 0xffff_ffff_ffff_ffff - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff - else: - self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff - - else: - # uses local time as initial seed - initRand = SplitMix64() - self._state = initRand() - -#===== end of module pcg64_32.py ======================================= diff --git a/PyRandLib/splitmix.py b/PyRandLib/splitmix.py deleted file mode 100644 index 2d674e8..0000000 --- a/PyRandLib/splitmix.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -import time - -from .annotation_types import Numerical - - -#============================================================================= -class SplitMix64: - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 64 bits. It implements the - 64-bits version of the Fast Splittable Pseudorandom Number - Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine - H. Flood in "Fast splittable pseudorandom number generators.", in - ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. - - It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) - in 2015, provided under the Creative Commons license and modified - under the same license by D. Lemire by Aug. 2017. - (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - if isinstance( _seed, int ): - self.state = self.__call__( _seed ) - - elif isinstance( _seed, float ): - # transforms passed initial seed from float to integer - if _seed < 0.0 : - _seed = -_seed - - if _seed >= 1.0: - self._state = self.__call__( round(_seed) ) - else: - self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) - - else: - # uses system local time - self._state = self.__call__( int(time.time() * 1000.0) ) - - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - if _seed is not None: - self._state = _seed & 0xffff_ffff_ffff_ffff - - self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff - self._state &= 0xffff_ffff_ffff_ffff - - z = self._state - z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff - z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff - - return z ^ (z >> 31) - - -#============================================================================= -class SplitMix63( SplitMix64 ): - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 63 bits. - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - super().__init__( _seed ) - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - # returns the 63 higher bits generated by base class operator () - return super().__call__( _seed ) >> 1 - - -#============================================================================= -class SplitMix32( SplitMix64 ): - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 32 bits. - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - super().__init__( _seed ) - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - # returns the 32 higher bits generated by base class operator () - return super().__call__( _seed ) >> 32 diff --git a/PyRandLib/squares32.py b/PyRandLib/squares32.py deleted file mode 100644 index f916093..0000000 --- a/PyRandLib/squares32.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basesquares import BaseSquares -from .annotation_types import SeedStateType, StatesList - - -#============================================================================= -class Squares32( BaseSquares ): - """ - Pseudo-random numbers generator - Squares pseudo-random Generators - dedicated to 64-bits calculations and 32-bits output values with - small period (min 2^64, i.e. 1.84e+19) but short computation time. - All Squares algorithms offer multi streams features, by simply - using different initial settings for control value 'key'. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This Squares models is based on a four rounds of squaring and - exchanging of upper and lower bits of the successive combinations. - Output values are provided on 32-bits or on 64-bits according to - the model. See [9] in README.md. - - See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 64-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Squares32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Return a 32-bits value. - """ - self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff - - y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff - z = (y + self._key) & 0xffff_ffff_ffff_ffff - # round 1 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 2 - x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 3 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 4 - return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 - -#===== end of module squares32.py ====================================== diff --git a/PyRandLib/squares64.py b/PyRandLib/squares64.py deleted file mode 100644 index 573f2e2..0000000 --- a/PyRandLib/squares64.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basesquares import BaseSquares -from .annotation_types import SeedStateType, StatesList - - -#============================================================================= -class Squares64( BaseSquares ): - """ - Pseudo-random numbers generator - Squares pseudo-random Generators - dedicated to 64-bits calculations and 32-bits output values with - small period (min 2^64, i.e. 1.84e+19) but short computation time. - All Squares algorithms offer multi streams features, by simply - using different initial settings for control value 'key'. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This Squares models is based on a four rounds of squaring and - exchanging of upper and lower bits of the successive combinations. - Output values are provided on 32-bits or on 64-bits according to - the model. See [9] in README.md. - Caution: this 64-bits output values version should not pass the - birthday test, which is a randmoness issue, while this is not - mentionned in the original paper (see [9] in file README.md). - - See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 32-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Squares32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Returns a 64-bits value. - """ - self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff - - y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff - z = (y + self._key) & 0xffff_ffff_ffff_ffff - # round 1 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 2 - x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 3 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 4 - t = x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 5 - return t ^ (((x * x + y) >> 32) & 0xffff_ffff) - -#===== end of module squares64.py ====================================== diff --git a/PyRandLib/well1024a.py b/PyRandLib/well1024a.py deleted file mode 100644 index 9f92c62..0000000 --- a/PyRandLib/well1024a.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well1024a( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well1024a() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well1024a() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - i_1 = (i - 1) & 0x1f - - z0 = self._state[i_1] - # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great - # simplification for the implementation of the generic WELL algorithm when evaluating z0. - z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) - # notice: the transformation applied to self._state[i] for Well1024a - # is the identity which leads to simplification also - z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) - # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a - # version, the zero matrix _M0 which we suppress here for calculations optimization purpose - - self._index = i_1 - return z3 - -#===== end of module well1024a.py ====================================== diff --git a/PyRandLib/well19937c.py b/PyRandLib/well19937c.py deleted file mode 100644 index 7b836e0..0000000 --- a/PyRandLib/well19937c.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well19937c( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well19937c() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well19937c() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - if i >= 2: - i_1, i_2 = i - 1, i - 2 - elif i == 1: - i_1, i_2 = 0, 623 - else: - i_1, i_2 = 623, 622 - - z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) - z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) - z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) - self._index = i_1 - - return self._tempering(z3, 0xe46e1700, 0x9b868000) - -#===== end of module well19937c.py ===================================== diff --git a/PyRandLib/well44497b.py b/PyRandLib/well44497b.py deleted file mode 100644 index 0157139..0000000 --- a/PyRandLib/well44497b.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well44497b( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well44497b() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well44497b() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - if i >= 2: - i_1, i_2 = i - 1, i - 2 - elif i == 1: - i_1, i_2 = 0, 1390 - else: - i_1, i_2 = 1390, 1389 - - z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) - z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) - z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 - self._index = i_1 - - return self._tempering(z3, 0x93dd1400, 0xfa118000) - -#===== end of module Well44497b.py ===================================== diff --git a/PyRandLib/well512a.py b/PyRandLib/well512a.py deleted file mode 100644 index 42b14a0..0000000 --- a/PyRandLib/well512a.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well512a( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well512a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well512a() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well512a() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - i_1 = (i - 1) & 0xf - - z0 = self._state[i_1] - # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great - # simplification for the implementation of the generic WELL algorithm when evaluating z0. - z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) - z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) - # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a - # version, the zero matrix _M0 which we suppress here for calculations optimization purpose - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) - - self._index = i_1 - return z3 - -#===== end of module well512a.py ======================================= diff --git a/PyRandLib/xoroshiro1024.py b/PyRandLib/xoroshiro1024.py deleted file mode 100644 index 62d5cb6..0000000 --- a/PyRandLib/xoroshiro1024.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, SeedStateType, StatesListAndExt -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro1024( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from - zeroland (100 iterations) and passes TestU01 tests. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro1024** of the algorithm. - - See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear - PRNG, with low computation time, 64-bits output values and good randomness - characteristics. - See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Furthermore this class is callable: - rand = Xoroshiro1024() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - #------------------------------------------------------------------------- - _STATE_SIZE : int = 16 - _SIZE_MODULO: int = 0xf # optimization here, to use operand & - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - ''' - const int q = p; - const uint64_t s0 = s[p = (p + 1) & 15]; - uint64_t s15 = s[q]; - const uint64_t result_plus = s0 + s15; - const uint64_t result_plusplus = rotl(s0 + s15, R) + s15; - const uint64_t result_star = s0 * S; - const uint64_t result_starstar = rotl(s0 * S, R) * T; - s15 ^= s0; - s[q] = rotl(s0, A) ^ s15 ^ (s15 << B); - s[p] = rotl(s15, C); - ''' - previousIndex = self._index - # advances the internal state of the PRNG - self._index += 1 - self._index &= self._SIZE_MODULO - sLow = self._state[ self._index ] - sHigh = self._state[ previousIndex ] ^ sLow - self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) - self._state[ self._index ] = self._rotleft( sHigh, 36 ) - # returns the output value - return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) & self._SIZE_MODULO - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module xoroshiro1024.py ================================== - diff --git a/PyRandLib/xoroshiro256.py b/PyRandLib/xoroshiro256.py deleted file mode 100644 index 0b09b63..0000000 --- a/PyRandLib/xoroshiro256.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, StatesList -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro256( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from - zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats - flaws, with a bad Hamming weight near zero (see - https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro256** of the algorithm. - - See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Implementation notice: the internal state of this PRNG is coded on four integers - rather than on a list of four integers, to optimize computations time. - - Furthermore this class is callable: - rand = Xoroshiro256() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - currentS1 = self._s1 - # advances the internal state of the PRNG - self._s2 ^= self._s0 - self._s3 ^= self._s1 - self._s1 ^= self._s2 - self._s0 ^= self._s3 - self._s2 ^= (currentS1 << 17) & self._MODULO - self._s3 = self._rotleft( self._s3, 45 ) - # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._s0 = initRand() - self._s1 = initRand() - self._s2 = initRand() - self._s3 = initRand() - - -#===== end of module xoroshiro256.py =================================== - diff --git a/PyRandLib/xoroshiro512.py b/PyRandLib/xoroshiro512.py deleted file mode 100644 index 73ed1c8..0000000 --- a/PyRandLib/xoroshiro512.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, StatesList -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro512( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from - zeroland (30 iterations only) and passes TestU01 tests. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro512** of the algorithm. - - See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear - PRNG, with low computation time, 64-bits output values and good randomness - characteristics. - See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Furthermore this class is callable: - rand = Xoroshiro512() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - currentS1 = self._state[1] - # advances the internal state of the PRNG - self._state[2] ^= self._state[0] - self._state[5] ^= self._state[1] - self._state[1] ^= self._state[2] - self._state[7] ^= self._state[3] - self._state[3] ^= self._state[4] - self._state[4] ^= self._state[5] - self._state[0] ^= self._state[6] - self._state[6] ^= self._state[7] - self._state[6] ^= (currentS1 << 11) & self._MODULO - self._state[7] = self._rotleft( self._state[7], 21 ) - # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(8) ] - - -#===== end of module xoroshiro512.py =================================== - From 4e9b5e2f8abf9079967733e792dd680438f94bd6 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 13:07:14 +0100 Subject: [PATCH 12/14] remove former subdirectory PyRandLib #145 Done. --- testCPUPerfs.py | 81 --------------------------- testED.py | 142 ------------------------------------------------ 2 files changed, 223 deletions(-) delete mode 100644 testCPUPerfs.py delete mode 100644 testED.py diff --git a/testCPUPerfs.py b/testCPUPerfs.py deleted file mode 100644 index 2c7b532..0000000 --- a/testCPUPerfs.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -import sys -from time import perf_counter_ns -from timeit import repeat - -from PyRandLib import * - - -#============================================================================= -def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): - """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" - print("---", prng_class_name, "---") - perfs = repeat("rnd.next()", - setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", - repeat=n_repeats, - timer=perf_counter_ns, - number=n_loops) - print([1e-9 * p / n_loops for p in perfs]) - print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") - - -#============================================================================= -if __name__ == "__main__": - - print("=== PyRandLib CPU time performances ===") - print("Python version:", sys.version, '\n') - - N = 15 - - test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) - test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) - test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) - test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) - test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) - test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) - test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) - test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) - test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) - test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) - - -#===== end of module testCPUPerfs.py =================================== diff --git a/testED.py b/testED.py deleted file mode 100644 index 91a4a67..0000000 --- a/testED.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from math import sqrt -from statistics import mean, median, stdev -from PyRandLib import * - - -#============================================================================= -def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): - """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. - - This module is provided with library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The Pseudo-Random Numbers Generators implemented in library PyRandLib have - been chosen as being the best in class ones about their randmoness quality - - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard - Simard (Université de Montréal) in 'TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical - Software, vol.33 n.4, pp.22-40, August 2007'). - - One of the main characteristics of these PRNGs is the equidistribution of - the generated random numbers. Validating this equidistribution does not - ensure the correctness of any implementation BUT the failure of this - validation ensures a not correct implementation. This is the sole goal of - this litle script. - - This script runs an N-times loop on each algprithm. At each loop, it draws - a pseudo-random number in the interval [0; 1,000) and sets an histogram of - the drawings (1,000 entries). It then evaluates statistics values mean, - median and standard eviation for each histogram and, for each histogram - entry, evaluates its variance. Should mean value be far from N / 1,000 or - any variance get a too large value, the script outputs on console all - faulty values. - """ - algo_name = rnd_algo.__class__.__name__ - print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') - - hist = [0]*nb_entries - - expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean - expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation - expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance - - if expected_max_diff_mean_median < 0.5: - expected_max_diff_mean_median= 0.5 - - for _ in range(nb_loops): - n = int(rnd_algo() * nb_entries) - hist[n] += 1 - - # uncomment next line if you want to print the content of the histograms - #print(hist, '\n') - - print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") - mn, md, st = mean(hist), median(hist), stdev(hist) - print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") - - err = False - - if (abs(md - mn) > expected_max_diff_mean_median): - err = True - print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") - if (st > expected_max_stdev): - err = True - print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") - - min_variance = max_variance = 0.0 - - for i in range(nb_entries): - variance = (hist[i] - mn) / st - if abs(variance) > expected_max_variance: - print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") - err = True - if variance < min_variance: - min_variance = variance - elif variance > max_variance: - max_variance = variance - - print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') - print(f", min: {min(hist)}, max: {max(hist)}") - - if (not err): - print(" Test OK.") - print() - - -#============================================================================= -if __name__ == "__main__": - test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number - test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) - test_algo(Cwg128(), 3217, nb_loops = 2_000_000) - test_algo(FastRand32(), 3217, nb_loops = 2_000_000) - test_algo(FastRand63(), 3217, nb_loops = 2_000_000) - test_algo(LFib78(), 3217, nb_loops = 2_000_000) - test_algo(LFib116(), 3217, nb_loops = 2_000_000) - test_algo(LFib668(), 3217, nb_loops = 2_000_000) - test_algo(LFib1340(), 3217, nb_loops = 2_000_000) - test_algo(Melg607(), 3217, nb_loops = 2_000_000) - test_algo(Melg19937(), 3217, nb_loops = 2_000_000) - test_algo(Melg44497(), 3217, nb_loops = 2_000_000) - test_algo(Mrg287(), 3217, nb_loops = 2_000_000) - test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) - test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) - test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) - test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) - test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) - test_algo(Squares32(), 3217, nb_loops = 2_000_000) - test_algo(Squares64(), 3217, nb_loops = 2_000_000) - test_algo(Well512a(), 3217, nb_loops = 1_500_000) - test_algo(Well1024a(), 3217, nb_loops = 1_500_000) - test_algo(Well19937c(), 2029) # notice: 2029 is a prime number - test_algo(Well44497b(), 2029) - test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) - test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) - test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) - - - -#===== end of module testED.py ========================================= From f21c45db3fa66052c5c8cb406e0d029db2d260fe Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 18:16:47 +0100 Subject: [PATCH 13/14] Back-up README.md --- README.md | 322 ++++++++++++++++++++++++++---------------------------- 1 file changed, 156 insertions(+), 166 deletions(-) diff --git a/README.md b/README.md index 8b173ea..8c1b867 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,11 @@ Many best in class pseudo random generators grouped into one simple library. ## License -PyRandLib is distributed under the MIT license for its largest use. -If you decide to use this library, please add the copyright notice to your -software as stated in the LICENSE file. +PyRandLib is distributed under the MIT license for its largest use. +If you decide to use this library, please add the copyright notice to your software as stated in the LICENSE file. ``` -Copyright (c) 2016-2022 Philippe Schmouker, +Copyright (c) 2016-2025 Philippe Schmouker, Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,7 +31,6 @@ SOFTWARE. - ## Intro This library implements some of the best-in-class pseudo random generators as evaluated by Pierre L'Ecuyer and Richard Simard in their famous paper "TestU01: A C library for empirical testing of random number generators" (ACM Trans. Math. Softw. Vol. 33 N.4, August 2007 - see reference [1]. The reader will take benefit reading L'Ecuyer & Simard's paper. @@ -43,9 +41,8 @@ Latest version of **PyRandLib** is version **2.0**, released by March 2025. It p ### Why not Mersenne twister? -The Mersenne twister PRG proposed by Matsumoto and Nishimura - see [5] - is -the most widely used PRG. The Random class of module random in Python -implements this PRG. It is also implemented in C++ and Java standard +The Mersenne twister PRNG proposed by Matsumoto and Nishimura - see [5] - is the most widely used one. The Random class of module random in Python +implements this PRNG. It is also implemented in C++ and Java standard libraries for instance. It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, this PRNG is a little bit long to compute (up to 3 times than LCGs, 60% more than LFibs and a little bit less than MRGs, see below at section 'Architecture overview'). Moreover, it fails four of the hardest TestU01 tests. You can still use it as your preferred PRNG but **PyRandLib** implements many other PRNGs that are either far faster or far better in terms of generated pseudo-randomness than the Mersenne twister PRNG. @@ -62,18 +59,13 @@ Notice: distribution version to be installed via pip or easy-install in cmd tool ## Randomness evaluation -In [1], every known PRG at the time of the editing has been tested according -to three different sets of tests: -* _small crush_ is a small set of simple tests that quickly tests some of -the expected characteristics for a pretty good PRG; -* _crush_ is a bigger set of tests that test more deeply expected random -characteristics; -* _big crush_ is the ultimate set of difficult tests that any GOOD PRG -should definitively pass. - -We give you here below a copy of the resulting table for the PRGs that have -been implemented in PyRandLib plus the Mersenne twister one which is not -implemented in PyRandLib, as provided in [1]. +In [1], every known PRNG at the time of the editing has been tested according to three different sets of tests: +* **_small crush_** is a small set of simple tests that quickly tests some of the expected characteristics for a pretty good PRNG; +* **_crush_** is a bigger set of tests that test more deeply expected random characteristics; +* **_big crush_** is the ultimate set of difficult tests that any **good** PRNG should definitively pass. + +We give you here below a copy of the resulting table for the PRNGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. +We add in this table the evaluations provided by the authors of every new PRNGs that have been described after the publication of [1]. Fields may be missing then for them. A comparison of the computation times for all implemented PRNGs in **PyRandLib** is provided in an another belowing table. | PyRabndLib class | TU01 generator name (1) | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------------------------------- | --------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | @@ -161,18 +153,12 @@ The Python versions used for these evaluations in their related virtual environm (*missing values in empty columns are to come*) ## Implementation -Current implementation of PyRandLib uses Python 3.x with no Cython version. -It has been tested with Python 3.8 but should run with all of Python 3. +Current implementation of **PyRandLib** uses Python 3.x with no Cython version. +It has been initally tested with Python 3.8 but should run with all subversions of Python 3 since 3.6. -Note 1: PyRandLib version 1.1 and below should work with all versions of -Python 3. In version 1.2, we have added underscores in numerical constants -for the better readability of the code. This feature has been introduced in -Python 3.6. If you want to use PyRandLib version 1.2 or above with Python -3.5 or below, removing these underscores should be sufficient to have the -library running correctly. +Note 1: **PyRandLib** version 1.1 and below should work with all versions of Python 3. In version 1.2, we have added underscores in numerical constants for the better readability of the code. This feature has been introduced in Python 3.6. If you want to use PyRandLib version 1.2 or above with Python 3.5 or below, removing these underscores should be sufficient to have the library running correctly. -Note 2: no version or PyRandLib will ever be provided for Python 2 which is -a no more maintained version of the Python language. +Note 2: no version or **PyRandLib** will ever be provided for Python 2 which is a no more maintained version of the Python language. Note 3: since release **2.0** of **PyRandLib** directories have been created that are each dedicated to a version of Python : 3.6, 3.9, 3.10, etc. Each of these directories contains the sub-directory `PyRandLib\` with a specific implementation of the library, optimized for the version of Python it relates to. @@ -180,33 +166,31 @@ Note 4: a Cython version of **PyRandLib** will be delivered in a next major rele ## New in release 1.2 -This is available starting at version 1.2 of PyRandLib. The call operator -(i.e., '()') gets a new signature which is still backward compatible with -previous versions of this library. Its new use is described here below. The -implementation code can be found in class `BaseRandom`, in module -`baserandom.py`. +This is available starting at version 1.2 of **PyRandLib**. + +The call operator (i.e., '()') gets a new signature which is still backward compatible with previous versions of this library. Its new use is described here below. The implementation code can be found in class `BaseRandom`, in module `baserandom.py`. from fastrand63 import FastRand63 rand = FastRand63() - # prints a float random value ranging in [0.0, 1.0] + # prints a float random value ranging in [0.0, 1.0) print( rand() ) - # prints an integer random value ranging in [0, 5] + # prints an integer random value ranging in [0, 5) print( rand(5) ) - # prints a float random value ranging in [0.0, 20.0] + # prints a float random value ranging in [0.0, 20.0) print( rand(20.0) ) - # prints a list of 10 integer values each ranging in [0, 5] + # prints a list of 10 integer values each ranging in [0, 5) print( rand(5, 10) ) - # prints a list of 10 float values each ranging in [0.0, 1.0] + # prints a list of 10 float values each ranging in [0.0, 1.0) print( rand(times=10) ) # prints a list of 4 random values ranging respectively in - # [0, 5], [0.0, 50.0], [0.0, 500.0] and [0, 5000] + # [0, 5), [0.0, 50.0), [0.0, 500.0) and [0, 5000) print( rand(5, 50.0, 500.0, 5000) ) # a more complex call which prints something like: @@ -214,7 +198,7 @@ implementation code can be found in class `BaseRandom`, in module # [2, 34.22526698212995, 242.54183578253426, 2204, [5, 3, 5, 4, 2, 0, 1, 3]], # [0, 17.77303802057933, 417.70662295909983, 559, [4, 1, 5, 0, 5, 3, 0, 5]] ] print( rand( (5, 50.0, 500.0, 5000, [5]*8), times=3 ) ) - + ## New in release 2.0 Version 2.0 of **PyRandLib** implements some new other "recent" PRNGs - see them listed below. It also provides two test scripts, enhanced documentation and some other internal development features. Finally, it is splitted in many subdirectories each dedicated to a specific version of Python: Python3.6, Python3.9, Python3.10, etc. In each of these directories, library **PyRandLib** code is fully copied and modified to take benefit of the improvements on new Python versions syntax and features. Copy the one version of value for your application to get all **PyRandLib** stuff at its best for your needs. @@ -260,23 +244,22 @@ So, if you want to see what is currently going on for next release of **PyRandLi ## Architecture overview -Each of the implemented PRG is described in an independent module. The name -of the module is directly related to the name of the related class. +Each of the implemented PRNG is described in an independent module. The name of the module is directly related to the name of the related class. -### BaseRandom - the base class for all PRGs +### BaseRandom - the base class for all PRNGs -**BaseRandom** is the base class for every implemented PRG in library -**PyRandLib**. It inherits from the Python built-in class random.Random. It -aims at providing simple common behavior for all PRG classes of the library, -the most noticeable one being the 'callable' nature of every implemented -PRGs. +**BaseRandom** is the base class for every implemented PRNG in library **PyRandLib**. It inherits from the Python built-in class `random.Random`. It aims at providing simple common behavior for all PRNG classes of the library, the most noticeable one being the 'callable' nature of every implemented PRNG. -Inheriting from the Python built-in class random.Random, **BaseRandom** -provides access to many useful distribution functions as described in -later section **Inherited Distribution Functions**. +Inheriting from the Python built-in class random.Random, **BaseRandom** provides access to many useful distribution functions as described in later section **Inherited Distribution Functions**. -Furthermore, every inheriting class may override methods: +Furthermore, every inheriting class MUST override the next three methods (if not, they each raise a `NotImplementedError` exception when called): + +* next(), +* getstate() and +* setstate() + +and may override the next three methods: * random(), * seed(), @@ -329,18 +312,14 @@ This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as ### FastRand32 - 2^32 periodicity -**FastRand32** implements a Linear Congruential Generator dedicated to -32-bits calculations with very short period (about 4.3e+09) but very short +**FastRand32** implements a Linear Congruential Generator dedicated to 32-bits calculations with very short period (about 4.3e+09) but very short time computation. -LCG models evaluate pseudo-random numbers suites *x(i)* as a simple -mathematical function of *x(i-1)*: +LCG models evaluate pseudo-random numbers suites *x(i)* as a simple mathematical function of *x(i-1)*: x(i) = ( a * x(i-1) + c ) mod m -The implementation of **FastRand32** is based on (*a*=69069, *c*=1) since -these two values have evaluated to be the 'best' ones for LCGs within -TestU01 while m = 2^32. +The implementation of **FastRand32** is based on (*a*=69069, *c*=1) since these two values have evaluated to be the 'best' ones for LCGs within TestU01 with m = 2^32. Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use such pseudo-random numbers generators for serious simulation applications. @@ -348,18 +327,13 @@ Results are nevertheless considered to be poor as stated in the evaluation done ### FastRand63 - 2^63 periodicity -**FastRand63** implements a Linear Congruential Generator dedicated to -63-bits calculations with a short period (about 9.2e+18) and very short -time computation. +**FastRand63** implements a Linear Congruential Generator dedicated to 63-bits calculations with a short period (about 9.2e+18) and very short time computation. -LCG models evaluate pseudo-random numbers suites *x(i)* as a simple -mathematical function of *x(i-1)*: +LCG model evaluate pseudo-random numbers suites *x(i)* as a simple mathematical function of *x(i-1)*: x(i) = ( a * x(i-1) + c ) mod m -The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) -since these two values have evaluated to be the 'best' ones for LCGs within -TestU01 while *m* = 2^63. +The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) since these two values have evaluated to be the *best* ones for LCGs within TestU01 while *m* = 2^63. Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests than does FastRandom32. @@ -376,14 +350,10 @@ where op is an operation that can be - (substraction), * (multiplication), ^(bitwise exclusive-or). - -With the + or - operation, such generators are true MRGs. They offer very -large periods with the best known results in the evaluation of their -randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in their -paper. -The Marsa-LIBF4 version, i.e. **MRGRand287** implementation, uses the -recurrence: +With the + or - operation, such generators are MRGs. They offer very large periods with the best known results in the evaluation of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard while offering very low computation times. + +The implementation of **LFibRand78** is based on a Lagged Fibonacci generator (LFib) which uses the recurrence: x(i) = ( x(i-5) + x(i-17) ) mod 2^64 @@ -393,7 +363,7 @@ Please notice that the TestU01 article states that the operator should be '*' wh -### MRGRand1457 - 2^1457 periodicity +### LFibRand116 - 2^116 periodicity **LFibRand116** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence @@ -474,14 +444,20 @@ where op is an operation that can be - (substraction), * (multiplication), ^(bitwise exclusive-or). + +With the + or - operation, such generators are true MRGs. They offer very large periods with the best known results in the evaluation of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in their paper. + +The Marsa-LIBF4 version, i.e. **Mrg287** implementation, uses the recurrence: -With the + or - operation, such generators are MRGs. They offer very large -periods with the best known results in the evaluation of their randomness, -as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard -while offering very low computation times. + x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 -The implementation of **LFibRand78** is based on a Lagged Fibonacci -generator (LFib) which uses the recurrence: + + +### Mrg1457 - 2^1,457 periodicity + +**Mrg1457** implements a fast 31-bits Multiple Recursive Generator with a longer period than MRGRan287 (2^1457 vs. 2^287, i.e. 4.0e+438 vs. 2.5e+86) and 80 % more computation time but with much less memory space consumption (47 vs. 256 integers 32-bits coded). + +The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random generator proposed by Deng and Lin, see [2]. The DX-47-3 version uses the recurrence: x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) @@ -520,7 +496,7 @@ The underlying algorithm acts as an LCG associated with a bits permutation as it -### LFibRand116 - 2^116 periodicity +### Pcg1024_32 - 2^32,830 periodicity **Pcg1024_32** implements a fast 64-bits based state and 32-bits output Permutated Congruential Generator with a very large period (2^32,830, i.e. 6.53e+9,882) with low computation time and large memory space consumption (1,026 integers 32-bits coded). @@ -620,7 +596,7 @@ Since the base class **BaseRandom** inherits from the built-in class `random.Ran **betavariate**(self, alpha, beta) Beta distribution. -Conditions on the parameters are alpha > 0 and beta > 0. +Conditions on the parameters are alpha > 0 and beta > 0. Returned values range between 0 and 1. @@ -641,25 +617,15 @@ Chooses a random element from a non-empty sequence. 'seq' has to be non empty. **choices**(population, weights=None, *, cum_weights=None, k=1) Returns a *k* sized list of elements chosen from the population, with replacement. If the population is empty, raises IndexError. -If a weights sequence is specified, selections are made according to the -relative weights. Alternatively, if a cum_weights sequence is given, the -selections are made according to the cumulative weights (perhaps computed -using itertools.accumulate()). For example, the relative weights -[10, 5, 30, 5] are equivalent to the cumulative weights [10, 15, 45, 50]. -Internally, the relative weights are converted to cumulative weights before -making selections, so supplying the cumulative weights saves work. +If a *weights* sequence is specified, selections are made according to the relative weights. Alternatively, if a *cum_weights* sequence is given, the selections are made according to the cumulative weights (perhaps computed using `itertools.accumulate()`). +For example, the relative weights `[10, 5, 30, 5]` are equivalent to the cumulative weights `[10, 15, 45, 50]`. +Internally, the relative weights are converted to cumulative weights before making selections, so supplying the cumulative weights saves work. -If neither weights nor cum_weights are specified, selections are made with -equal probability. If a weights sequence is supplied, it must be the same -length as the population sequence. It is a TypeError to specify both -weights and cum_weights. +If neither `weights` nor `cum_weights` are specified, selections are made with equal probability. If a `weights` sequence is supplied, it must be the same length as the population sequence. It is a `TypeError` to specify both `weights` and `cum_weights`. -The weights or cum_weights can use any numeric type that interoperates with -the float values returned by random() (that includes integers, floats, and -fractions but excludes decimals). +The `weights` or `cum_weights` can use any numeric type that interoperates with the float values returned by random() (that includes integers, floats, and fractions but excludes decimals). -Notice: 'choices' has been provided since Python 3.6. It should be -implemented for older versions. +Notice: `choices` has been provided since Python 3.6. It should be implemented for older versions. **expovariate**(self, lambd=1.0) @@ -673,13 +639,13 @@ Since Python 3.12, the parameter `lambd` gets a default value in this built-in m **gammavariate**(self, alpha, beta) Gamma distribution. Not the gamma function! -Conditions on the parameters are alpha > 0 and beta > 0. +Conditions on the parameters are `alpha` > 0 and `beta` > 0. **gauss**(self, mu, sigma) Gaussian distribution. -mu is the mean, and sigma is the standard deviation. +mu is the mean, and sigma is the standard deviation. This is slightly faster than the normalvariate() function. Not thread-safe without a lock around calls. @@ -696,16 +662,14 @@ Returns internal state; can be passed to `setstate()` later. **lognormvariate**(self, mu, sigma) Log normal distribution. -If you take the natural logarithm of this distribution, you'll get a normal -distribution with mean mu and standard deviation sigma. -mu can have any value, and sigma must be greater than zero. +If you take the natural logarithm of this distribution, you'll get a normal distribution with mean `mu` and standard deviation `sigma`. +`mu` can have any value, and `sigma` must be greater than zero. **normalvariate**(self, mu, sigma) Normal distribution. -mu is the mean, and sigma is the standard deviation. See method gauss() for -a faster but not thread-safe equivalent. +`mu` is the mean, and `sigma` is the standard deviation. See method `gauss()` for a faster but not thread-safe equivalent. **paretovariate**(self, alpha) @@ -720,38 +684,27 @@ Returns a random integer in range [a, b], including both end points. **randrange**(self, start, stop=None, step=1) Returns a randomly selected element from range(start, stop, step). This is equivalent to `choice( range(start, stop, step) )` without building a range object. -The positional argument pattern matches that of range(). Keyword arguments -should not be used because the function may use them in unexpected ways. +The positional argument pattern matches that of `range()`. Keyword arguments should not be used because the function may use them in unexpected ways. **sample**(self, population, k) Chooses `k` unique random elements from a population sequence or set. -Returns a new list containing elements from the population while leaving -the original population unchanged. The resulting list is in selection -order so that all sub-slices will also be valid random samples. This allows -raffle winners (the sample) to be partitioned into grand prize and second -place winners (the subslices). +Returns a new list containing elements from the population while leaving the original population unchanged. The resulting list is in selection order so that all sub-slices will also be valid random samples. This allows raffle winners (the sample) to be partitioned into grand prize and second place winners (the subslices). -Members of the population need not be hashable or unique. If the population -contains repeats, then each occurrence is a possible selection in the -sample. +Members of the population need not be hashable or unique. If the population contains repeats, then each occurrence is a possible selection in the sample. -To choose a sample in a range of integers, use range as an argument. This -is especially fast and space efficient for sampling from a large -population: sample(range(10000000), 60) +To choose a sample in a range of integers, use range as an argument. This is especially fast and space efficient for sampling from a large population: `sample(range(10_000_000), 60)`. **seed**(self, a=None, version=2) Initialize internal state from hashable object. -None or no argument seeds from current time or from an operating system -specific randomness source if available. +None or no argument seeds from current time, or from an operating system specific randomness source if available. -For version 2 (the default), all of the bits are used if *a* is a str, -bytes, or bytearray. For version 1, the hash() of *a* is used instead. +For version 2 (the default), all of the bits are used if `a` is a str, bytes, or bytearray. For version 1, the hash() of `a` is used instead. -If *a* is an int, all bits are used. +If `a` is an int, all bits are used. **setstate**(self, state) @@ -761,30 +714,19 @@ Restores internal state from object returned by `getstate()`. **shuffle**(self, x, random=None) Shuffle the sequence x in place. Returns None. -The optional argument random is a 0-argument function returning a random -float in [0.0, 1.0); by default, this is the function random(). +The optional argument `random` is a 0-argument function returning a random float in [0.0, 1.0); by default, this is the function random(). -To shuffle an immutable sequence and return a new shuffled list, use -sample(x, k=len(x)) instead. +To shuffle an immutable sequence and return a new shuffled list, use `sample(x, k=len(x))` instead. -Note that even for small len(x), the total number of permutations of x can -quickly grow larger than the period of most random number generators. This -implies that most permutations of a long sequence can never be generated. -For example, a sequence of length 2080 is the largest that can fit within -the period of the Mersenne Twister random number generator. +Note that even for small `len(x)`, the total number of permutations of `x` can quickly grow larger than the period of most random number generators. This implies that most permutations of a long sequence can never be generated. For example, a sequence of length 2080 is the largest that can fit within the period of the Mersenne Twister random number generator. **triangular**(self, low=0.0, high=1.0, mode=None) Triangular distribution. -Continuous distribution bounded by given lower and upper limits, and having -a given mode value in-between. Returns a random floating point number *N* -such that low <= *N* <= high and with the specified mode between those -bounds. The low and high bounds default to zero and one. The mode argument -defaults to the midpoint between the bounds, giving a symmetric -distribution. +Continuous distribution bounded by given lower and upper limits, and having a given mode value in-between. Returns a random floating point number *N* such that `low` <= *N* <= `high` and with the specified mode between those bounds. The `low` and `high` bounds default to zero and one. The mode argument defaults to the midpoint between the bounds, giving a symmetric distribution. -http://en.wikipedia.org/wiki/Triangular_distribution +see [http://en.wikipedia.org/wiki/Triangular_distribution](http://en.wikipedia.org/wiki/Triangular_distribution) **uniform**(self, a, b) @@ -794,25 +736,22 @@ Gets a random number in the range [`a`, `b`) or [`a`, `b`] depending on rounding **vonmisesvariate**(self, mu, kappa) Circular data distribution. -mu is the mean angle, expressed in radians between 0 and 2*pi, and kappa is -the concentration parameter, which must be greater than or equal to zero. -If kappa is equal to zero, this distribution reduces to a uniform random -angle over the range 0 to 2*pi. +`mu` is the mean angle, expressed in radians between `0` and `2*pi`, and `kappa` is the concentration parameter, which must be greater than or equal to zero. If `kappa` is equal to zero, this distribution reduces to a uniform random angle over the range `0` to `2*pi`. **weibullvariate**(self, alpha, beta) Weibull distribution. -alpha is the scale parameter and beta is the shape parameter. +`alpha` is the scale parameter and `beta` is the shape parameter. ## References -**[1]** Pierre L'Ecuyer and Richard Simard. 2007. -*TestU01: A C library for empirical testing of random number generators*. -ACM Transaction on Mathematical Software, Vol.33 N.4, Article 22 (August 2007), 40 pages. DOI: http://dx.doi.org/10.1145/1268776.1268777 - +**[1]** Pierre L'Ecuyer and Richard Simard. 2007. +*TestU01: A C library for empirical testing of random number generators*. +In ACM Transaction on Mathematical Software, Vol.33 N.4, Article 22 (August 2007), 40 pages. +DOI: http://dx.doi.org/10.1145/1268776.1268777 BibTex: @article{L'Ecuyer:2007:TCL:1268776.1268777, author = {L'Ecuyer, Pierre and Simard, Richard}, @@ -836,10 +775,9 @@ BibTex: } -**[2]** Lih-Yuan Deng & Dennis K. J. Lin. 2000. -*Random number generation for the new century*. +**[2]** Lih-Yuan Deng & Dennis K. J. Lin. 2000. +*Random number generation for the new century*. The American Statistician Vol.54, N.2, pp. 145–150. - BibTex: @article{doi:10.1080/00031305.2000.10474528, author = { Lih-Yuan Deng and Dennis K. J. Lin }, @@ -850,23 +788,75 @@ number = {2}, pages = {145-150}, year = {2000}, doi = {10.1080/00031305.2000.10474528}, -URL = {ttp://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, +URL = {http://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, eprint = {http://amstat.tandfonline.com/doi/pdf/10.1080/00031305.2000.10474528} } -**[3]** Lih-Yuan Deng. 2005. -*Efficient and portable multiple recursive generators of large order*. -ACM Transactions on Modeling and Computer. Simulation 15:1. +**[3]** Lih-Yuan Deng. 2005. +*Efficient and portable multiple recursive generators of large order*. +In ACM Transactions on Modeling and Computer Simulation, Jan. 2005, Vol. 15 Issue 1, pp. 1-13. +DOI: https://doi.org/10.1145/1044322.1044323 + + +**[4]** Georges Marsaglia. 1985. +*A current view of random number generators*. +In Computer Science and Statistics, Sixteenth Symposium on the Interface. +Elsevier Science Publishers, North-Holland, Amsterdam, 1985, The Netherlands, pp. 3–10. + + +**[5]** Makoto Matsumoto and Takuji Nishimura. 1998. +*Mersenne twister: A 623-dimensionally equidistributed uniform pseudo-random number generator.* +In ACM Transactions on Modeling and Computer Simulation (TOMACS) - Special issue on uniform random number generation. Vol.8 N.1, Jan. 1998, pp. 3-30. + + +**[6]** François Panneton and Pierre L'Ecuyer (Université de Montréal) and Makoto Matsumoto (Hiroshima University). 2006. +*Improved Long-Period Generators Based on Linear Recurrences Modulo 2*. +In ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, pp. 1–16. +see [https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf](https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + + +**[7]** Melissa E. O'Neill. 2014. +*PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation*. +Submitted to ACM Transactions on Mathematical Software (47 pages) +Finally: Harvey Mudd College Computer Science Department Technical Report, HMC-CS-2014-0905, Issued: September 5, 2014 (56 pages). +@techreport{oneill:pcg2014, + title = "PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation", + author = "Melissa E. O'Neill", + institution = "Harvey Mudd College", + address = "Claremont, CA", + number = "HMC-CS-2014-0905", + year = "2014", + month = Sep, + xurl = "https://www.cs.hmc.edu/tr/hmc-cs-2014-0905.pdf", +} +see also [https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf](https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf). + + +**[8]** Tomasz R. Dziala. 2023. +*Collatz-Weyl Generators: High Quality and High Throughput Parameterized Pseudorandom Number Generators*. +Published at arXiv, December 2023 (11 pages), +Last reference: arXiv:2312.17043v4 [cs.CE], 2 Dec 2024, see [https://arxiv.org/abs/2312.17043](https://arxiv.org/abs/2312.17043) +DOI: https://doi.org/10.48550/arXiv.2312.17043 + + +**[9]** Bernard Widynski. March 2022. +*Squares: A Fast Counter-Based RNG*. +Published at arXiv, March 2022 (5 pages) +Last reference: arXiv:2004.06278v7 [cs.DS] 13 Mar 2022, see [https://arxiv.org/pdf/2004.06278](https://arxiv.org/pdf/2004.06278). +DOI: https://doi.org/10.48550/arXiv.2004.06278 -**[4]** Georges Marsaglia. 1985. -*A current view of random number generators*. -In Computer Science and Statistics, Sixteenth Symposium on the Interface. Elsevier Science Publishers, North-Holland, -Amsterdam, 1985, The Netherlands. pp. 3–10. +**[10]** David Blackman, Sebastiano Vigna. 2018. +*Scrambled Linear Pseudorandom Number Generators*. +Published in arXiv, March 2022 (32 pages) +Last reference: arXiv:1805.01407v3 [cs.DS] 28 Mar 2022, see [https://arxiv.org/pdf/1805.01407](https://arxiv.org/pdf/1805.01407). +DOI: https://doi.org/10.48550/arXiv.1805.01407 -**[5]** Makoto Matsumoto and Takuji Nishimura. 1998. -*Mersenne twister: A 623-dimensionally equidistributed uniform pseudo-random number generator.* -In ACM Transactions on Modeling and Computer Simulation (TOMACS) - Special issue on uniform random number generation. -Vol.8 N.1, Jan. 1998, pp. 3-30. +**[11]** Shin Harase, Takamitsu Kimoto, 2018. +*Implementing 64-bit Maximally Equidistributed F2-Linear Generators with Mersenne Prime Period*. +In ACM Transactions on Mathematical Software, Volume 44, Issue 3, April 2018, Article No. 30 (11 Pages) +Also published in arXiv, March 2022 (11 pages) +Last reference: arXiv:1505.06582v6 [cs.DS] 20 Nov 2017, see [https://arxiv.org/pdf/1505.06582](https://arxiv.org/pdf/1505.06582). +DOI: https://doi.org/10.1145/3159444, https://doi.org/10.48550/arXiv.1505.06582 From ecaf4cb044826274ba22a3a26c75774d69352eff Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 18:26:41 +0100 Subject: [PATCH 14/14] #149-repair bad merging --- LICENSE | 2 +- PyRandLib-org/__init__.py | 44 ++ PyRandLib-org/annotation_types.py | 35 ++ PyRandLib-org/basecwg.py | 113 ++++ PyRandLib-org/baselcg.py | 104 ++++ PyRandLib-org/baselfib64.py | 205 +++++++ PyRandLib-org/basemelg.py | 199 +++++++ PyRandLib-org/basemrg.py | 183 ++++++ PyRandLib-org/basepcg.py | 117 ++++ PyRandLib-org/baserandom.py | 401 +++++++++++++ PyRandLib-org/basesquares.py | 165 ++++++ PyRandLib-org/basewell.py | 307 ++++++++++ PyRandLib-org/basexoroshiro.py | 190 +++++++ PyRandLib-org/cwg128.py | 159 ++++++ PyRandLib-org/cwg128_64.py | 153 +++++ PyRandLib-org/cwg64.py | 153 +++++ PyRandLib-org/fastrand32.py | 122 ++++ PyRandLib-org/fastrand63.py | 134 +++++ PyRandLib-org/lfib116.py | 133 +++++ PyRandLib-org/lfib1340.py | 134 +++++ PyRandLib-org/lfib668.py | 133 +++++ PyRandLib-org/lfib78.py | 132 +++++ PyRandLib-org/melg19937.py | 121 ++++ PyRandLib-org/melg44497.py | 120 ++++ PyRandLib-org/melg607.py | 120 ++++ PyRandLib-org/mrg1457.py | 148 +++++ PyRandLib-org/mrg287.py | 151 +++++ PyRandLib-org/mrg49507.py | 141 +++++ PyRandLib-org/pcg1024_32.py | 280 +++++++++ PyRandLib-org/pcg128_64.py | 176 ++++++ PyRandLib-org/pcg64_32.py | 155 +++++ PyRandLib-org/splitmix.py | 138 +++++ PyRandLib-org/squares32.py | 110 ++++ PyRandLib-org/squares64.py | 132 +++++ PyRandLib-org/well1024a.py | 136 +++++ PyRandLib-org/well19937c.py | 135 +++++ PyRandLib-org/well44497b.py | 135 +++++ PyRandLib-org/well512a.py | 135 +++++ PyRandLib-org/xoroshiro1024.py | 204 +++++++ PyRandLib-org/xoroshiro256.py | 182 ++++++ PyRandLib-org/xoroshiro512.py | 178 ++++++ PyRandLib/LICENSE | 21 + PyRandLib/README.md | 686 +++++++++++++++++++++++ PyRandLib/__init__.py | 44 ++ PyRandLib/annotation_types.py | 35 ++ PyRandLib/basecwg.py | 113 ++++ PyRandLib/baselcg.py | 104 ++++ PyRandLib/baselfib64.py | 205 +++++++ PyRandLib/basemelg.py | 199 +++++++ PyRandLib/basemrg.py | 183 ++++++ PyRandLib/basepcg.py | 117 ++++ PyRandLib/baserandom.py | 401 +++++++++++++ PyRandLib/basesquares.py | 165 ++++++ PyRandLib/basewell.py | 307 ++++++++++ PyRandLib/basexoroshiro.py | 190 +++++++ PyRandLib/cwg128.py | 159 ++++++ PyRandLib/cwg128_64.py | 153 +++++ PyRandLib/cwg64.py | 153 +++++ PyRandLib/fastrand32.py | 122 ++++ PyRandLib/fastrand63.py | 134 +++++ PyRandLib/lfib116.py | 133 +++++ PyRandLib/lfib1340.py | 134 +++++ PyRandLib/lfib668.py | 133 +++++ PyRandLib/lfib78.py | 132 +++++ PyRandLib/melg19937.py | 121 ++++ PyRandLib/melg44497.py | 120 ++++ PyRandLib/melg607.py | 120 ++++ PyRandLib/mrg1457.py | 148 +++++ PyRandLib/mrg287.py | 151 +++++ PyRandLib/mrg49507.py | 141 +++++ PyRandLib/mrgrand1457.py | 135 +++++ PyRandLib/mrgrand287.py | 155 +++++ PyRandLib/mrgrand49507.py | 130 +++++ PyRandLib/pcg1024_32.py | 280 +++++++++ PyRandLib/pcg128_64.py | 176 ++++++ PyRandLib/pcg64_32.py | 155 +++++ PyRandLib/splitmix.py | 138 +++++ PyRandLib/squares32.py | 110 ++++ PyRandLib/squares64.py | 132 +++++ PyRandLib/types.py | 33 ++ PyRandLib/well1024a.py | 136 +++++ PyRandLib/well19937c.py | 135 +++++ PyRandLib/well44497b.py | 135 +++++ PyRandLib/well512a.py | 135 +++++ PyRandLib/xoroshiro1024.py | 204 +++++++ PyRandLib/xoroshiro256.py | 182 ++++++ PyRandLib/xoroshiro512.py | 178 ++++++ Python3.10/PyRandLib/__init__.py | 44 ++ Python3.10/PyRandLib/annotation_types.py | 33 ++ Python3.10/PyRandLib/basecwg.py | 113 ++++ Python3.10/PyRandLib/baselcg.py | 105 ++++ Python3.10/PyRandLib/baselfib64.py | 206 +++++++ Python3.10/PyRandLib/basemelg.py | 200 +++++++ Python3.10/PyRandLib/basemrg.py | 182 ++++++ Python3.10/PyRandLib/basepcg.py | 118 ++++ Python3.10/PyRandLib/basesquares.py | 161 ++++++ Python3.10/PyRandLib/basewell.py | 289 ++++++++++ Python3.10/PyRandLib/basexoroshiro.py | 189 +++++++ Python3.10/PyRandLib/cwg128.py | 160 ++++++ Python3.10/PyRandLib/cwg128_64.py | 153 +++++ Python3.10/PyRandLib/cwg64.py | 153 +++++ Python3.10/PyRandLib/fastrand32.py | 121 ++++ Python3.10/PyRandLib/fastrand63.py | 133 +++++ Python3.10/PyRandLib/lfib116.py | 134 +++++ Python3.10/PyRandLib/lfib1340.py | 136 +++++ Python3.10/PyRandLib/lfib668.py | 135 +++++ Python3.10/PyRandLib/lfib78.py | 134 +++++ Python3.10/PyRandLib/melg19937.py | 120 ++++ Python3.10/PyRandLib/melg44497.py | 119 ++++ Python3.10/PyRandLib/melg607.py | 119 ++++ Python3.10/PyRandLib/mrg1457.py | 149 +++++ Python3.10/PyRandLib/mrg287.py | 150 +++++ Python3.10/PyRandLib/mrg49507.py | 140 +++++ Python3.10/PyRandLib/pcg1024_32.py | 281 ++++++++++ Python3.10/PyRandLib/pcg128_64.py | 175 ++++++ Python3.10/PyRandLib/pcg64_32.py | 154 +++++ Python3.10/PyRandLib/splitmix.py | 141 +++++ Python3.10/PyRandLib/squares32.py | 111 ++++ Python3.10/PyRandLib/squares64.py | 133 +++++ Python3.10/PyRandLib/well1024a.py | 138 +++++ Python3.10/PyRandLib/well19937c.py | 140 +++++ Python3.10/PyRandLib/well44497b.py | 140 +++++ Python3.10/PyRandLib/well512a.py | 139 +++++ Python3.10/PyRandLib/xoroshiro1024.py | 191 +++++++ Python3.10/PyRandLib/xoroshiro256.py | 180 ++++++ Python3.10/PyRandLib/xoroshiro512.py | 182 ++++++ Python3.10/testCPUPerfs.py | 81 +++ Python3.10/testED.py | 142 +++++ Python3.11/PyRandLib/__init__.py | 44 ++ Python3.11/PyRandLib/annotation_types.py | 33 ++ Python3.11/PyRandLib/basecwg.py | 113 ++++ Python3.11/PyRandLib/baselcg.py | 105 ++++ Python3.11/PyRandLib/baselfib64.py | 206 +++++++ Python3.11/PyRandLib/basemelg.py | 200 +++++++ Python3.11/PyRandLib/basemrg.py | 182 ++++++ Python3.11/PyRandLib/basepcg.py | 118 ++++ Python3.11/PyRandLib/basesquares.py | 161 ++++++ Python3.11/PyRandLib/basewell.py | 289 ++++++++++ Python3.11/PyRandLib/basexoroshiro.py | 189 +++++++ Python3.11/PyRandLib/cwg128.py | 160 ++++++ Python3.11/PyRandLib/cwg128_64.py | 153 +++++ Python3.11/PyRandLib/cwg64.py | 153 +++++ Python3.11/PyRandLib/fastrand32.py | 121 ++++ Python3.11/PyRandLib/fastrand63.py | 133 +++++ Python3.11/PyRandLib/lfib116.py | 134 +++++ Python3.11/PyRandLib/lfib1340.py | 136 +++++ Python3.11/PyRandLib/lfib668.py | 135 +++++ Python3.11/PyRandLib/lfib78.py | 134 +++++ Python3.11/PyRandLib/melg19937.py | 120 ++++ Python3.11/PyRandLib/melg44497.py | 119 ++++ Python3.11/PyRandLib/melg607.py | 119 ++++ Python3.11/PyRandLib/mrg1457.py | 149 +++++ Python3.11/PyRandLib/mrg287.py | 150 +++++ Python3.11/PyRandLib/mrg49507.py | 140 +++++ Python3.11/PyRandLib/pcg1024_32.py | 281 ++++++++++ Python3.11/PyRandLib/pcg128_64.py | 175 ++++++ Python3.11/PyRandLib/pcg64_32.py | 154 +++++ Python3.11/PyRandLib/splitmix.py | 141 +++++ Python3.11/PyRandLib/squares32.py | 111 ++++ Python3.11/PyRandLib/squares64.py | 133 +++++ Python3.11/PyRandLib/well1024a.py | 138 +++++ Python3.11/PyRandLib/well19937c.py | 140 +++++ Python3.11/PyRandLib/well44497b.py | 140 +++++ Python3.11/PyRandLib/well512a.py | 139 +++++ Python3.11/PyRandLib/xoroshiro1024.py | 191 +++++++ Python3.11/PyRandLib/xoroshiro256.py | 180 ++++++ Python3.11/PyRandLib/xoroshiro512.py | 182 ++++++ Python3.11/testCPUPerfs.py | 81 +++ Python3.11/testED.py | 142 +++++ Python3.6/PyRandLib/__init__.py | 47 ++ Python3.6/PyRandLib/annotation_types.py | 35 ++ Python3.6/PyRandLib/basecwg.py | 113 ++++ Python3.6/PyRandLib/baselcg.py | 104 ++++ Python3.6/PyRandLib/baselfib64.py | 205 +++++++ Python3.6/PyRandLib/basemelg.py | 199 +++++++ Python3.6/PyRandLib/basemrg.py | 183 ++++++ Python3.6/PyRandLib/basepcg.py | 118 ++++ Python3.6/PyRandLib/basesquares.py | 165 ++++++ Python3.6/PyRandLib/basewell.py | 288 ++++++++++ Python3.6/PyRandLib/basexoroshiro.py | 188 +++++++ Python3.6/PyRandLib/cwg128.py | 158 ++++++ Python3.6/PyRandLib/cwg128_64.py | 152 +++++ Python3.6/PyRandLib/cwg64.py | 152 +++++ Python3.6/PyRandLib/fastrand32.py | 123 ++++ Python3.6/PyRandLib/fastrand63.py | 133 +++++ Python3.6/PyRandLib/lfib116.py | 134 +++++ Python3.6/PyRandLib/lfib1340.py | 135 +++++ Python3.6/PyRandLib/lfib668.py | 134 +++++ Python3.6/PyRandLib/lfib78.py | 133 +++++ Python3.6/PyRandLib/melg19937.py | 121 ++++ Python3.6/PyRandLib/melg44497.py | 120 ++++ Python3.6/PyRandLib/melg607.py | 120 ++++ Python3.6/PyRandLib/mrg1457.py | 149 +++++ Python3.6/PyRandLib/mrg287.py | 152 +++++ Python3.6/PyRandLib/mrg49507.py | 141 +++++ Python3.6/PyRandLib/pcg1024_32.py | 280 +++++++++ Python3.6/PyRandLib/pcg128_64.py | 175 ++++++ Python3.6/PyRandLib/pcg64_32.py | 156 ++++++ Python3.6/PyRandLib/splitmix.py | 140 +++++ Python3.6/PyRandLib/squares32.py | 111 ++++ Python3.6/PyRandLib/squares64.py | 135 +++++ Python3.6/PyRandLib/well1024a.py | 138 +++++ Python3.6/PyRandLib/well19937c.py | 137 +++++ Python3.6/PyRandLib/well44497b.py | 137 +++++ Python3.6/PyRandLib/well512a.py | 136 +++++ Python3.6/PyRandLib/xoroshiro1024.py | 169 ++++++ Python3.6/PyRandLib/xoroshiro256.py | 183 ++++++ Python3.6/PyRandLib/xoroshiro512.py | 160 ++++++ Python3.6/testCPUPerfs.py | 81 +++ Python3.6/testED.py | 141 +++++ Python3.9/PyRandLib/__init__.py | 44 ++ Python3.9/PyRandLib/annotation_types.py | 35 ++ Python3.9/PyRandLib/basecwg.py | 113 ++++ Python3.9/PyRandLib/baselcg.py | 105 ++++ Python3.9/PyRandLib/baselfib64.py | 207 +++++++ Python3.9/PyRandLib/basemelg.py | 201 +++++++ Python3.9/PyRandLib/basemrg.py | 183 ++++++ Python3.9/PyRandLib/basepcg.py | 118 ++++ Python3.9/PyRandLib/basesquares.py | 161 ++++++ Python3.9/PyRandLib/basewell.py | 290 ++++++++++ Python3.9/PyRandLib/basexoroshiro.py | 190 +++++++ Python3.9/PyRandLib/cwg128.py | 160 ++++++ Python3.9/PyRandLib/cwg128_64.py | 153 +++++ Python3.9/PyRandLib/cwg64.py | 153 +++++ Python3.9/PyRandLib/fastrand32.py | 121 ++++ Python3.9/PyRandLib/fastrand63.py | 133 +++++ Python3.9/PyRandLib/lfib116.py | 134 +++++ Python3.9/PyRandLib/lfib1340.py | 136 +++++ Python3.9/PyRandLib/lfib668.py | 135 +++++ Python3.9/PyRandLib/lfib78.py | 134 +++++ Python3.9/PyRandLib/melg19937.py | 120 ++++ Python3.9/PyRandLib/melg44497.py | 119 ++++ Python3.9/PyRandLib/melg607.py | 119 ++++ Python3.9/PyRandLib/mrg1457.py | 149 +++++ Python3.9/PyRandLib/mrg287.py | 150 +++++ Python3.9/PyRandLib/mrg49507.py | 140 +++++ Python3.9/PyRandLib/pcg1024_32.py | 282 ++++++++++ Python3.9/PyRandLib/pcg128_64.py | 175 ++++++ Python3.9/PyRandLib/pcg64_32.py | 154 +++++ Python3.9/PyRandLib/splitmix.py | 141 +++++ Python3.9/PyRandLib/squares32.py | 111 ++++ Python3.9/PyRandLib/squares64.py | 133 +++++ Python3.9/PyRandLib/well1024a.py | 138 +++++ Python3.9/PyRandLib/well19937c.py | 137 +++++ Python3.9/PyRandLib/well44497b.py | 137 +++++ Python3.9/PyRandLib/well512a.py | 139 +++++ Python3.9/PyRandLib/xoroshiro1024.py | 190 +++++++ Python3.9/PyRandLib/xoroshiro256.py | 181 ++++++ Python3.9/PyRandLib/xoroshiro512.py | 181 ++++++ Python3.9/testCPUPerfs.py | 81 +++ Python3.9/testED.py | 142 +++++ testCPUPerfs.py | 81 +++ testED.py | 142 +++++ 253 files changed, 37864 insertions(+), 1 deletion(-) create mode 100644 PyRandLib-org/__init__.py create mode 100644 PyRandLib-org/annotation_types.py create mode 100644 PyRandLib-org/basecwg.py create mode 100644 PyRandLib-org/baselcg.py create mode 100644 PyRandLib-org/baselfib64.py create mode 100644 PyRandLib-org/basemelg.py create mode 100644 PyRandLib-org/basemrg.py create mode 100644 PyRandLib-org/basepcg.py create mode 100644 PyRandLib-org/baserandom.py create mode 100644 PyRandLib-org/basesquares.py create mode 100644 PyRandLib-org/basewell.py create mode 100644 PyRandLib-org/basexoroshiro.py create mode 100644 PyRandLib-org/cwg128.py create mode 100644 PyRandLib-org/cwg128_64.py create mode 100644 PyRandLib-org/cwg64.py create mode 100644 PyRandLib-org/fastrand32.py create mode 100644 PyRandLib-org/fastrand63.py create mode 100644 PyRandLib-org/lfib116.py create mode 100644 PyRandLib-org/lfib1340.py create mode 100644 PyRandLib-org/lfib668.py create mode 100644 PyRandLib-org/lfib78.py create mode 100644 PyRandLib-org/melg19937.py create mode 100644 PyRandLib-org/melg44497.py create mode 100644 PyRandLib-org/melg607.py create mode 100644 PyRandLib-org/mrg1457.py create mode 100644 PyRandLib-org/mrg287.py create mode 100644 PyRandLib-org/mrg49507.py create mode 100644 PyRandLib-org/pcg1024_32.py create mode 100644 PyRandLib-org/pcg128_64.py create mode 100644 PyRandLib-org/pcg64_32.py create mode 100644 PyRandLib-org/splitmix.py create mode 100644 PyRandLib-org/squares32.py create mode 100644 PyRandLib-org/squares64.py create mode 100644 PyRandLib-org/well1024a.py create mode 100644 PyRandLib-org/well19937c.py create mode 100644 PyRandLib-org/well44497b.py create mode 100644 PyRandLib-org/well512a.py create mode 100644 PyRandLib-org/xoroshiro1024.py create mode 100644 PyRandLib-org/xoroshiro256.py create mode 100644 PyRandLib-org/xoroshiro512.py create mode 100644 PyRandLib/LICENSE create mode 100644 PyRandLib/README.md create mode 100644 PyRandLib/__init__.py create mode 100644 PyRandLib/annotation_types.py create mode 100644 PyRandLib/basecwg.py create mode 100644 PyRandLib/baselcg.py create mode 100644 PyRandLib/baselfib64.py create mode 100644 PyRandLib/basemelg.py create mode 100644 PyRandLib/basemrg.py create mode 100644 PyRandLib/basepcg.py create mode 100644 PyRandLib/baserandom.py create mode 100644 PyRandLib/basesquares.py create mode 100644 PyRandLib/basewell.py create mode 100644 PyRandLib/basexoroshiro.py create mode 100644 PyRandLib/cwg128.py create mode 100644 PyRandLib/cwg128_64.py create mode 100644 PyRandLib/cwg64.py create mode 100644 PyRandLib/fastrand32.py create mode 100644 PyRandLib/fastrand63.py create mode 100644 PyRandLib/lfib116.py create mode 100644 PyRandLib/lfib1340.py create mode 100644 PyRandLib/lfib668.py create mode 100644 PyRandLib/lfib78.py create mode 100644 PyRandLib/melg19937.py create mode 100644 PyRandLib/melg44497.py create mode 100644 PyRandLib/melg607.py create mode 100644 PyRandLib/mrg1457.py create mode 100644 PyRandLib/mrg287.py create mode 100644 PyRandLib/mrg49507.py create mode 100644 PyRandLib/mrgrand1457.py create mode 100644 PyRandLib/mrgrand287.py create mode 100644 PyRandLib/mrgrand49507.py create mode 100644 PyRandLib/pcg1024_32.py create mode 100644 PyRandLib/pcg128_64.py create mode 100644 PyRandLib/pcg64_32.py create mode 100644 PyRandLib/splitmix.py create mode 100644 PyRandLib/squares32.py create mode 100644 PyRandLib/squares64.py create mode 100644 PyRandLib/types.py create mode 100644 PyRandLib/well1024a.py create mode 100644 PyRandLib/well19937c.py create mode 100644 PyRandLib/well44497b.py create mode 100644 PyRandLib/well512a.py create mode 100644 PyRandLib/xoroshiro1024.py create mode 100644 PyRandLib/xoroshiro256.py create mode 100644 PyRandLib/xoroshiro512.py create mode 100644 Python3.10/PyRandLib/__init__.py create mode 100644 Python3.10/PyRandLib/annotation_types.py create mode 100644 Python3.10/PyRandLib/basecwg.py create mode 100644 Python3.10/PyRandLib/baselcg.py create mode 100644 Python3.10/PyRandLib/baselfib64.py create mode 100644 Python3.10/PyRandLib/basemelg.py create mode 100644 Python3.10/PyRandLib/basemrg.py create mode 100644 Python3.10/PyRandLib/basepcg.py create mode 100644 Python3.10/PyRandLib/basesquares.py create mode 100644 Python3.10/PyRandLib/basewell.py create mode 100644 Python3.10/PyRandLib/basexoroshiro.py create mode 100644 Python3.10/PyRandLib/cwg128.py create mode 100644 Python3.10/PyRandLib/cwg128_64.py create mode 100644 Python3.10/PyRandLib/cwg64.py create mode 100644 Python3.10/PyRandLib/fastrand32.py create mode 100644 Python3.10/PyRandLib/fastrand63.py create mode 100644 Python3.10/PyRandLib/lfib116.py create mode 100644 Python3.10/PyRandLib/lfib1340.py create mode 100644 Python3.10/PyRandLib/lfib668.py create mode 100644 Python3.10/PyRandLib/lfib78.py create mode 100644 Python3.10/PyRandLib/melg19937.py create mode 100644 Python3.10/PyRandLib/melg44497.py create mode 100644 Python3.10/PyRandLib/melg607.py create mode 100644 Python3.10/PyRandLib/mrg1457.py create mode 100644 Python3.10/PyRandLib/mrg287.py create mode 100644 Python3.10/PyRandLib/mrg49507.py create mode 100644 Python3.10/PyRandLib/pcg1024_32.py create mode 100644 Python3.10/PyRandLib/pcg128_64.py create mode 100644 Python3.10/PyRandLib/pcg64_32.py create mode 100644 Python3.10/PyRandLib/splitmix.py create mode 100644 Python3.10/PyRandLib/squares32.py create mode 100644 Python3.10/PyRandLib/squares64.py create mode 100644 Python3.10/PyRandLib/well1024a.py create mode 100644 Python3.10/PyRandLib/well19937c.py create mode 100644 Python3.10/PyRandLib/well44497b.py create mode 100644 Python3.10/PyRandLib/well512a.py create mode 100644 Python3.10/PyRandLib/xoroshiro1024.py create mode 100644 Python3.10/PyRandLib/xoroshiro256.py create mode 100644 Python3.10/PyRandLib/xoroshiro512.py create mode 100644 Python3.10/testCPUPerfs.py create mode 100644 Python3.10/testED.py create mode 100644 Python3.11/PyRandLib/__init__.py create mode 100644 Python3.11/PyRandLib/annotation_types.py create mode 100644 Python3.11/PyRandLib/basecwg.py create mode 100644 Python3.11/PyRandLib/baselcg.py create mode 100644 Python3.11/PyRandLib/baselfib64.py create mode 100644 Python3.11/PyRandLib/basemelg.py create mode 100644 Python3.11/PyRandLib/basemrg.py create mode 100644 Python3.11/PyRandLib/basepcg.py create mode 100644 Python3.11/PyRandLib/basesquares.py create mode 100644 Python3.11/PyRandLib/basewell.py create mode 100644 Python3.11/PyRandLib/basexoroshiro.py create mode 100644 Python3.11/PyRandLib/cwg128.py create mode 100644 Python3.11/PyRandLib/cwg128_64.py create mode 100644 Python3.11/PyRandLib/cwg64.py create mode 100644 Python3.11/PyRandLib/fastrand32.py create mode 100644 Python3.11/PyRandLib/fastrand63.py create mode 100644 Python3.11/PyRandLib/lfib116.py create mode 100644 Python3.11/PyRandLib/lfib1340.py create mode 100644 Python3.11/PyRandLib/lfib668.py create mode 100644 Python3.11/PyRandLib/lfib78.py create mode 100644 Python3.11/PyRandLib/melg19937.py create mode 100644 Python3.11/PyRandLib/melg44497.py create mode 100644 Python3.11/PyRandLib/melg607.py create mode 100644 Python3.11/PyRandLib/mrg1457.py create mode 100644 Python3.11/PyRandLib/mrg287.py create mode 100644 Python3.11/PyRandLib/mrg49507.py create mode 100644 Python3.11/PyRandLib/pcg1024_32.py create mode 100644 Python3.11/PyRandLib/pcg128_64.py create mode 100644 Python3.11/PyRandLib/pcg64_32.py create mode 100644 Python3.11/PyRandLib/splitmix.py create mode 100644 Python3.11/PyRandLib/squares32.py create mode 100644 Python3.11/PyRandLib/squares64.py create mode 100644 Python3.11/PyRandLib/well1024a.py create mode 100644 Python3.11/PyRandLib/well19937c.py create mode 100644 Python3.11/PyRandLib/well44497b.py create mode 100644 Python3.11/PyRandLib/well512a.py create mode 100644 Python3.11/PyRandLib/xoroshiro1024.py create mode 100644 Python3.11/PyRandLib/xoroshiro256.py create mode 100644 Python3.11/PyRandLib/xoroshiro512.py create mode 100644 Python3.11/testCPUPerfs.py create mode 100644 Python3.11/testED.py create mode 100644 Python3.6/PyRandLib/__init__.py create mode 100644 Python3.6/PyRandLib/annotation_types.py create mode 100644 Python3.6/PyRandLib/basecwg.py create mode 100644 Python3.6/PyRandLib/baselcg.py create mode 100644 Python3.6/PyRandLib/baselfib64.py create mode 100644 Python3.6/PyRandLib/basemelg.py create mode 100644 Python3.6/PyRandLib/basemrg.py create mode 100644 Python3.6/PyRandLib/basepcg.py create mode 100644 Python3.6/PyRandLib/basesquares.py create mode 100644 Python3.6/PyRandLib/basewell.py create mode 100644 Python3.6/PyRandLib/basexoroshiro.py create mode 100644 Python3.6/PyRandLib/cwg128.py create mode 100644 Python3.6/PyRandLib/cwg128_64.py create mode 100644 Python3.6/PyRandLib/cwg64.py create mode 100644 Python3.6/PyRandLib/fastrand32.py create mode 100644 Python3.6/PyRandLib/fastrand63.py create mode 100644 Python3.6/PyRandLib/lfib116.py create mode 100644 Python3.6/PyRandLib/lfib1340.py create mode 100644 Python3.6/PyRandLib/lfib668.py create mode 100644 Python3.6/PyRandLib/lfib78.py create mode 100644 Python3.6/PyRandLib/melg19937.py create mode 100644 Python3.6/PyRandLib/melg44497.py create mode 100644 Python3.6/PyRandLib/melg607.py create mode 100644 Python3.6/PyRandLib/mrg1457.py create mode 100644 Python3.6/PyRandLib/mrg287.py create mode 100644 Python3.6/PyRandLib/mrg49507.py create mode 100644 Python3.6/PyRandLib/pcg1024_32.py create mode 100644 Python3.6/PyRandLib/pcg128_64.py create mode 100644 Python3.6/PyRandLib/pcg64_32.py create mode 100644 Python3.6/PyRandLib/splitmix.py create mode 100644 Python3.6/PyRandLib/squares32.py create mode 100644 Python3.6/PyRandLib/squares64.py create mode 100644 Python3.6/PyRandLib/well1024a.py create mode 100644 Python3.6/PyRandLib/well19937c.py create mode 100644 Python3.6/PyRandLib/well44497b.py create mode 100644 Python3.6/PyRandLib/well512a.py create mode 100644 Python3.6/PyRandLib/xoroshiro1024.py create mode 100644 Python3.6/PyRandLib/xoroshiro256.py create mode 100644 Python3.6/PyRandLib/xoroshiro512.py create mode 100644 Python3.6/testCPUPerfs.py create mode 100644 Python3.6/testED.py create mode 100644 Python3.9/PyRandLib/__init__.py create mode 100644 Python3.9/PyRandLib/annotation_types.py create mode 100644 Python3.9/PyRandLib/basecwg.py create mode 100644 Python3.9/PyRandLib/baselcg.py create mode 100644 Python3.9/PyRandLib/baselfib64.py create mode 100644 Python3.9/PyRandLib/basemelg.py create mode 100644 Python3.9/PyRandLib/basemrg.py create mode 100644 Python3.9/PyRandLib/basepcg.py create mode 100644 Python3.9/PyRandLib/basesquares.py create mode 100644 Python3.9/PyRandLib/basewell.py create mode 100644 Python3.9/PyRandLib/basexoroshiro.py create mode 100644 Python3.9/PyRandLib/cwg128.py create mode 100644 Python3.9/PyRandLib/cwg128_64.py create mode 100644 Python3.9/PyRandLib/cwg64.py create mode 100644 Python3.9/PyRandLib/fastrand32.py create mode 100644 Python3.9/PyRandLib/fastrand63.py create mode 100644 Python3.9/PyRandLib/lfib116.py create mode 100644 Python3.9/PyRandLib/lfib1340.py create mode 100644 Python3.9/PyRandLib/lfib668.py create mode 100644 Python3.9/PyRandLib/lfib78.py create mode 100644 Python3.9/PyRandLib/melg19937.py create mode 100644 Python3.9/PyRandLib/melg44497.py create mode 100644 Python3.9/PyRandLib/melg607.py create mode 100644 Python3.9/PyRandLib/mrg1457.py create mode 100644 Python3.9/PyRandLib/mrg287.py create mode 100644 Python3.9/PyRandLib/mrg49507.py create mode 100644 Python3.9/PyRandLib/pcg1024_32.py create mode 100644 Python3.9/PyRandLib/pcg128_64.py create mode 100644 Python3.9/PyRandLib/pcg64_32.py create mode 100644 Python3.9/PyRandLib/splitmix.py create mode 100644 Python3.9/PyRandLib/squares32.py create mode 100644 Python3.9/PyRandLib/squares64.py create mode 100644 Python3.9/PyRandLib/well1024a.py create mode 100644 Python3.9/PyRandLib/well19937c.py create mode 100644 Python3.9/PyRandLib/well44497b.py create mode 100644 Python3.9/PyRandLib/well512a.py create mode 100644 Python3.9/PyRandLib/xoroshiro1024.py create mode 100644 Python3.9/PyRandLib/xoroshiro256.py create mode 100644 Python3.9/PyRandLib/xoroshiro512.py create mode 100644 Python3.9/testCPUPerfs.py create mode 100644 Python3.9/testED.py create mode 100644 testCPUPerfs.py create mode 100644 testED.py diff --git a/LICENSE b/LICENSE index 612929c..21b3417 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com. +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PyRandLib-org/__init__.py b/PyRandLib-org/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/PyRandLib-org/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib-org/annotation_types.py b/PyRandLib-org/annotation_types.py new file mode 100644 index 0000000..e097004 --- /dev/null +++ b/PyRandLib-org/annotation_types.py @@ -0,0 +1,35 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Tuple, Union + +Numerical = Union[ int, float ] +StatesList = Union[ Tuple[int], List[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/PyRandLib-org/basecwg.py b/PyRandLib-org/basecwg.py new file mode 100644 index 0000000..40c2aa5 --- /dev/null +++ b/PyRandLib-org/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/PyRandLib-org/baselcg.py b/PyRandLib-org/baselcg.py new file mode 100644 index 0000000..acd6ce2 --- /dev/null +++ b/PyRandLib-org/baselcg.py @@ -0,0 +1,104 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module baselcg.py ======================================== diff --git a/PyRandLib-org/baselfib64.py b/PyRandLib-org/baselfib64.py new file mode 100644 index 0000000..96da67d --- /dev/null +++ b/PyRandLib-org/baselfib64.py @@ -0,0 +1,205 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/PyRandLib-org/basemelg.py b/PyRandLib-org/basemelg.py new file mode 100644 index 0000000..e69665b --- /dev/null +++ b/PyRandLib-org/basemelg.py @@ -0,0 +1,199 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/PyRandLib-org/basemrg.py b/PyRandLib-org/basemrg.py new file mode 100644 index 0000000..8059c8e --- /dev/null +++ b/PyRandLib-org/basemrg.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/PyRandLib-org/basepcg.py b/PyRandLib-org/basepcg.py new file mode 100644 index 0000000..8c9a423 --- /dev/null +++ b/PyRandLib-org/basepcg.py @@ -0,0 +1,117 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module basepcg.py ======================================== diff --git a/PyRandLib-org/baserandom.py b/PyRandLib-org/baserandom.py new file mode 100644 index 0000000..12b8598 --- /dev/null +++ b/PyRandLib-org/baserandom.py @@ -0,0 +1,401 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random +from typing import List, Tuple, Union + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | expovariate(self, lambd) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def seed(self, _seed: SeedStateType = None) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Union[Numerical, + Tuple[Numerical], + List[Numerical]] = 1.0, + times: int = 1 ) -> Union[Numerical, List[Numerical]]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + loMask = (1 << (_bitsCount - _rotCount)) - 1 + hiMask = ((1 << _bitsCount) - 1) ^ loMask + hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) + return ((_value & loMask) << _rotCount) | hiBits + + +#===== end of module baserandom.py ===================================== diff --git a/PyRandLib-org/basesquares.py b/PyRandLib-org/basesquares.py new file mode 100644 index 0000000..2968237 --- /dev/null +++ b/PyRandLib-org/basesquares.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/PyRandLib-org/basewell.py b/PyRandLib-org/basewell.py new file mode 100644 index 0000000..b7dc2ac --- /dev/null +++ b/PyRandLib-org/basewell.py @@ -0,0 +1,307 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return y ^ a if x & (1 << t) else y + + #------------------------------------------------------------------------- + @classmethod + def _d(cls, s: int) -> int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + #assert 0 <= w <= 32 + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + @property + def _a1(self): + return 0xda44_2d24 + + @property + def _a2(self): + return 0xd3e4_3ffd + + @property + def _a3(self): + return 0x8bdc_b91e + + @property + def _a4(self): + return 0x86a9_d87e + + @property + def _a5(self): + return 0xa8c2_96d1 + + @property + def _a6(self): + return 0x5d6b_45cc + + @property + def _a7(self): + return 0xb729_fcec + +#===== end of module basewell.py ======================================= diff --git a/PyRandLib-org/basexoroshiro.py b/PyRandLib-org/basexoroshiro.py new file mode 100644 index 0000000..1226391 --- /dev/null +++ b/PyRandLib-org/basexoroshiro.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: int = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> List[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/PyRandLib-org/cwg128.py b/PyRandLib-org/cwg128.py new file mode 100644 index 0000000..22fbab2 --- /dev/null +++ b/PyRandLib-org/cwg128.py @@ -0,0 +1,159 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: int = (1 << 128) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + +#===== end of module cwg128.py ========================================= diff --git a/PyRandLib-org/cwg128_64.py b/PyRandLib-org/cwg128_64.py new file mode 100644 index 0000000..5f7a936 --- /dev/null +++ b/PyRandLib-org/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/PyRandLib-org/cwg64.py b/PyRandLib-org/cwg64.py new file mode 100644 index 0000000..7b34c77 --- /dev/null +++ b/PyRandLib-org/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/PyRandLib-org/fastrand32.py b/PyRandLib-org/fastrand32.py new file mode 100644 index 0000000..9813c66 --- /dev/null +++ b/PyRandLib-org/fastrand32.py @@ -0,0 +1,122 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix32( _state ) + self._state = initRand() + else: + initRand = SplitMix32() + self._state = initRand() + +#===== end of module fastrand32.py ===================================== diff --git a/PyRandLib-org/fastrand63.py b/PyRandLib-org/fastrand63.py new file mode 100644 index 0000000..4483c40 --- /dev/null +++ b/PyRandLib-org/fastrand63.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix63( _state ) + self._state = initRand() + else: + initRand = SplitMix63() + self._state = initRand() + + +#===== end of module fastrand63.py ===================================== diff --git a/PyRandLib-org/lfib116.py b/PyRandLib-org/lfib116.py new file mode 100644 index 0000000..8f1f000 --- /dev/null +++ b/PyRandLib-org/lfib116.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k24 = self._index-24 + if k24 < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + +#===== end of module lfib116.py ======================================== diff --git a/PyRandLib-org/lfib1340.py b/PyRandLib-org/lfib1340.py new file mode 100644 index 0000000..5007045 --- /dev/null +++ b/PyRandLib-org/lfib1340.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + k861 = self._index-861 + if k861 < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + +#===== end of module lfib1340.py ====================================== diff --git a/PyRandLib-org/lfib668.py b/PyRandLib-org/lfib668.py new file mode 100644 index 0000000..0f5e494 --- /dev/null +++ b/PyRandLib-org/lfib668.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + k273 = self._index-273 + if k273 < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + +#===== end of module lfib668.py ======================================= diff --git a/PyRandLib-org/lfib78.py b/PyRandLib-org/lfib78.py new file mode 100644 index 0000000..7f3589c --- /dev/null +++ b/PyRandLib-org/lfib78.py @@ -0,0 +1,132 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k5 = self._index-5 + if k5 < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib78._STATE_SIZE + + return myValue + +#===== end of module lfib78.py ========================================= diff --git a/PyRandLib-org/melg19937.py b/PyRandLib-org/melg19937.py new file mode 100644 index 0000000..5d9b093 --- /dev/null +++ b/PyRandLib-org/melg19937.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 311 + self._index = i_1 + + s311 = self._state[311] + + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) + self._state[311] = s311 + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib-org/melg44497.py b/PyRandLib-org/melg44497.py new file mode 100644 index 0000000..286cdd7 --- /dev/null +++ b/PyRandLib-org/melg44497.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 695 + self._index = i_1 + + s695 = self._state[695] + + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) + self._state[695] = s695 + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib-org/melg607.py b/PyRandLib-org/melg607.py new file mode 100644 index 0000000..6a38a55 --- /dev/null +++ b/PyRandLib-org/melg607.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 9 + self._index = i_1 + + s9 = self._state[9] + + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) # M=5, s1=13 + self._state[9] = s9 + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) # s2=35 + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) # s3=30, L=3, b = 0x66ed... + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib-org/mrg1457.py b/PyRandLib-org/mrg1457.py new file mode 100644 index 0000000..17f8c32 --- /dev/null +++ b/PyRandLib-org/mrg1457.py @@ -0,0 +1,148 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + k1 = self._index-1 + if k1 < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + k24 = self._index-24 + if k24 < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib-org/mrg287.py b/PyRandLib-org/mrg287.py new file mode 100644 index 0000000..119c3f0 --- /dev/null +++ b/PyRandLib-org/mrg287.py @@ -0,0 +1,151 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + k55 = self._index-55 + if k55 < 0: + k55 += Mrg287._STATE_SIZE + + k119 = self._index-119 + if k119 < 0: + k119 += Mrg287._STATE_SIZE + + k179 = self._index-179 + if k179 < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % self._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib-org/mrg49507.py b/PyRandLib-org/mrg49507.py new file mode 100644 index 0000000..2eb2597 --- /dev/null +++ b/PyRandLib-org/mrg49507.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + k7 = self._index-7 + if k7 < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrgrand49507.py =================================== diff --git a/PyRandLib-org/pcg1024_32.py b/PyRandLib-org/pcg1024_32.py new file mode 100644 index 0000000..c721499 --- /dev/null +++ b/PyRandLib-org/pcg1024_32.py @@ -0,0 +1,280 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _EXTENDED_STATE_SIZE: int = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == self._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + result ^= result >> 22 + + self._extendedState[i] = result + + return result == state & 0b11 + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/PyRandLib-org/pcg128_64.py b/PyRandLib-org/pcg128_64.py new file mode 100644 index 0000000..6f9eb84 --- /dev/null +++ b/PyRandLib-org/pcg128_64.py @@ -0,0 +1,176 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO_128 : int = (1 << 128) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & self._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + current_state ^= current_state >> 64 # fixed shift XOR is then evaluated + value = current_state & 0xffff_ffff_ffff_ffff + rot_mask = (1 << random_rotation) - 1 + return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & self._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & self._MODULO_128 + else: + self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + +#===== end of module pcg128_64.py ====================================== diff --git a/PyRandLib-org/pcg64_32.py b/PyRandLib-org/pcg64_32.py new file mode 100644 index 0000000..1fa867e --- /dev/null +++ b/PyRandLib-org/pcg64_32.py @@ -0,0 +1,155 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + current_state ^= current_state >> 22 # fixed shift XOR is then evaluated + return (current_state >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = initRand() + +#===== end of module pcg64_32.py ======================================= diff --git a/PyRandLib-org/splitmix.py b/PyRandLib-org/splitmix.py new file mode 100644 index 0000000..2d674e8 --- /dev/null +++ b/PyRandLib-org/splitmix.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 diff --git a/PyRandLib-org/squares32.py b/PyRandLib-org/squares32.py new file mode 100644 index 0000000..f916093 --- /dev/null +++ b/PyRandLib-org/squares32.py @@ -0,0 +1,110 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + +#===== end of module squares32.py ====================================== diff --git a/PyRandLib-org/squares64.py b/PyRandLib-org/squares64.py new file mode 100644 index 0000000..573f2e2 --- /dev/null +++ b/PyRandLib-org/squares64.py @@ -0,0 +1,132 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + +#===== end of module squares64.py ====================================== diff --git a/PyRandLib-org/well1024a.py b/PyRandLib-org/well1024a.py new file mode 100644 index 0000000..9f92c62 --- /dev/null +++ b/PyRandLib-org/well1024a.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + +#===== end of module well1024a.py ====================================== diff --git a/PyRandLib-org/well19937c.py b/PyRandLib-org/well19937c.py new file mode 100644 index 0000000..7b836e0 --- /dev/null +++ b/PyRandLib-org/well19937c.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 623 + else: + i_1, i_2 = 623, 622 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) + z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) + self._index = i_1 + + return self._tempering(z3, 0xe46e1700, 0x9b868000) + +#===== end of module well19937c.py ===================================== diff --git a/PyRandLib-org/well44497b.py b/PyRandLib-org/well44497b.py new file mode 100644 index 0000000..0157139 --- /dev/null +++ b/PyRandLib-org/well44497b.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 1390 + else: + i_1, i_2 = 1390, 1389 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 + self._index = i_1 + + return self._tempering(z3, 0x93dd1400, 0xfa118000) + +#===== end of module Well44497b.py ===================================== diff --git a/PyRandLib-org/well512a.py b/PyRandLib-org/well512a.py new file mode 100644 index 0000000..42b14a0 --- /dev/null +++ b/PyRandLib-org/well512a.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + +#===== end of module well512a.py ======================================= diff --git a/PyRandLib-org/xoroshiro1024.py b/PyRandLib-org/xoroshiro1024.py new file mode 100644 index 0000000..62d5cb6 --- /dev/null +++ b/PyRandLib-org/xoroshiro1024.py @@ -0,0 +1,204 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + #------------------------------------------------------------------------- + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + ''' + const int q = p; + const uint64_t s0 = s[p = (p + 1) & 15]; + uint64_t s15 = s[q]; + const uint64_t result_plus = s0 + s15; + const uint64_t result_plusplus = rotl(s0 + s15, R) + s15; + const uint64_t result_star = s0 * S; + const uint64_t result_starstar = rotl(s0 * S, R) * T; + s15 ^= s0; + s[q] = rotl(s0, A) ^ s15 ^ (s15 << B); + s[p] = rotl(s15, C); + ''' + previousIndex = self._index + # advances the internal state of the PRNG + self._index += 1 + self._index &= self._SIZE_MODULO + sLow = self._state[ self._index ] + sHigh = self._state[ previousIndex ] ^ sLow + self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) + self._state[ self._index ] = self._rotleft( sHigh, 36 ) + # returns the output value + return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & self._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/PyRandLib-org/xoroshiro256.py b/PyRandLib-org/xoroshiro256.py new file mode 100644 index 0000000..0b09b63 --- /dev/null +++ b/PyRandLib-org/xoroshiro256.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & self._MODULO + self._s3 = self._rotleft( self._s3, 45 ) + # returns the output value + return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/PyRandLib-org/xoroshiro512.py b/PyRandLib-org/xoroshiro512.py new file mode 100644 index 0000000..73ed1c8 --- /dev/null +++ b/PyRandLib-org/xoroshiro512.py @@ -0,0 +1,178 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & self._MODULO + self._state[7] = self._rotleft( self._state[7], 21 ) + # returns the output value + return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(8) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/PyRandLib/LICENSE b/PyRandLib/LICENSE new file mode 100644 index 0000000..1e480cb --- /dev/null +++ b/PyRandLib/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PyRandLib/README.md b/PyRandLib/README.md new file mode 100644 index 0000000..5402b3a --- /dev/null +++ b/PyRandLib/README.md @@ -0,0 +1,686 @@ +# PyRandLib [![Latest release](http://img.shields.io/github/release/schmouk/pyrandlib.svg?style=plastic&labelColor=blueviolet&color=success)](https://github.com/schmouk/pyrandlib/releases) +Many best in class pseudo random generators grouped into one simple library. + + + +## License +PyRandLib is distributed under the MIT license for its largest use. +If you decide to use this library, please add the copyright notice to your +software as stated in the LICENSE file. + +``` +Copyright (c) 2016-2022 Philippe Schmouker, + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + + + + +## Intro +This library implements some of the best-in-class pseudo random generators +as evaluated by Pierre L'Ecuyer and Richard Simard in their famous paper +"TestU01: A C library for empirical testing of random number generators" +(ACM Trans. Math. Softw. Vol. 33 N.4, August 2007 - see reference [1]. The +reader will take benefit reading L'Ecuyer & Simard's paper. + +Each of the Pseudo Random Generator (PRG) implemented in PyRandLib is self +documented. Names of classes directly refer to the kind of PRG they implem- +ent augmented with some number characterizing their periodicity. All of +their randomness characteristics are explained in every related module. + + +### Why not Mersenne twister? + +The Mersenne twister PRG proposed by Matsumoto and Nishimura - see [5] - is +the most widely used PRG. The Random class of module random in Python +implements this PRG. It is also implemented in C++ and Java standard +libraries for instance. + +It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, +this PRG is a little bit long to compute (up to 3 times than LCGs, 60% more +than LFibs and a little bit less than MRGs, see below at section 'Architect- +ure overview'). Moreover, it fails 4 of the hardest TestU01 tests. You can +still use it as your preferred PRG but PyRandLib implements many other PRGs +which are either far faster or far better in terms of generated pseudo- +randomness than the Mersenne twister PRG. + + + +## Installation +Currently, the only way to install PyRandLib is to download the .zip or +.tar.gz archive, then to directly put sub-directory 'PyRandLib' from archive +into directory 'site-packages', in the main directory 'Lib' of your Python +environment. See https://schmouk.github.io/PyRandLib/ for an easy access to +download versions or click on tab **releases** on home page of GitHub +repository. + +A distribution version (to be installed via pip or easy-install in cmd tool +or in console) is to come. + + + +## Randomness evaluation +In [1], every known PRG at the time of the editing has been tested according +to three different sets of tests: +* _small crush_ is a small set of simple tests that quickly tests some of +the expected characteristics for a pretty good PRG; +* _crush_ is a bigger set of tests that test more deeply expected random +characteristics; +* _big crush_ is the ultimate set of difficult tests that any GOOD PRG +should definitively pass. + +We give you here below a copy of the resulting table for the PRGs that have +been implemented in PyRandLib plus the Mersenne twister one which is not +implemented in PyRabdLib, as provided in [1]. + + | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | ---------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19937 | 4.30 | 1.6 | 0 | 2 | 2 | + + + +## Implementation +Current implementation of PyRandLib uses Python 3.x with no Cython version. +It has been tested with Python 3.8 but should run with all of Python 3. + +Note 1: PyRandLib version 1.1 and below should work with all versions of +Python 3. In version 1.2, we have added underscores in numerical constants +for the better readability of the code. This feature has been introduced in +Python 3.6. If you want to use PyRandLib version 1.2 or above with Python +3.5 or below, removing these underscores should be sufficient to have the +library running correctly. + +Note 2: no version or PyRandLib will ever be provided for Python 2 which is +a no more maintained version of the Python language. + +Note 3: a Cython version of PyRandLib might be delivered in a next release. +Up today, no date is planned for this. + + +## New in release 1.2 +This is available starting at version 1.2 of PyRandLib. The call operator +(i.e., '()') gets a new signature which is still backward compatible with +previous versions of this library. Its new use is described here below. The +implementation code can be found in class `BaseRandom`, in module +`baserandom.py`. + + from fastrand63 import FastRand63 + + rand = FastRand63() + + # prints a float random value ranging in [0.0, 1.0] + print( rand() ) + + # prints an integer random value ranging in [0, 5] + print( rand(5) ) + + # prints a float random value ranging in [0.0, 20.0] + print( rand(20.0) + + # prints a list of 10 integer values each ranging in [0, 5] + print( rand(5, 10) ) + + # prints a list of 10 float values each ranging in [0.0, 1.0] + print( rand(times=10) ) + + # prints a list of 4 random values ranging respectively in + # [0, 5], [0.0, 50.0], [0.0, 500.0] and [0, 5000] + print( rand(5, 50.0, 500.0, 5000) ) + + # a more complex call which prints something like: + # [ [3, 11.64307079016269, 127.65395855782158, 4206, [2, 0, 1, 4, 4, 1, 2, 0]], + # [2, 34.22526698212995, 242.54183578253426, 2204, [5, 3, 5, 4, 2, 0, 1, 3]], + # [0, 17.77303802057933, 417.70662295909983, 559, [4, 1, 5, 0, 5, 3, 0, 5]] ] + print( rand( (5, 50.0, 500.0, 5000, [5]*8), times=3 ) ) + + + +## Architecture overview +Each of the implemented PRG is described in an independent module. The name +of the module is directly related to the name of the related class. + + +### BaseRandom - the base class for all PRGs + +**BaseRandom** is the base class for every implemented PRG in library +**PyRandLib**. It inherits from the Python built-in class random.Random. It +aims at providing simple common behavior for all PRG classes of the library, +the most noticeable one being the 'callable' nature of every implemented +PRGs. For instance: + + rand = BaseRandom() + print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) + print( rand(a,b) ) # prints a uniform pseudo-random value within [a, b) + +Inheriting from the Python built-in class random.Random, **BaseRandom** +provides access to many useful distribution functions as described in +later section **Inherited Distribution Functions**. + +Furthermore, every inheriting class may override methods: + +* random(), +* seed(), +* getrandbits(k), +* getstate() and +* setstate(). + +This lets inheriting classes implement the PRGs related core methods. + + + +### FastRand32 - 2^32 periodicity + +**FastRand32** implements a Linear Congruential Generator dedicated to +32-bits calculations with very short period (about 4.3e+09) but very short +time computation. + +LCG models evaluate pseudo-random numbers suites *x(i)* as a simple +mathematical function of *x(i-1)*: + + x(i) = ( a * x(i-1) + c ) mod m + +The implementation of **FastRand32** is based on (*a*=69069, *c*=1) since +these two values have evaluated to be the 'best' ones for LCGs within +TestU01 while m = 2^32. + +Results are nevertheless considered to be poor as stated in the evaluation +done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not +recommended to use such pseudo-random numbers generators for serious +simulation applications. + +See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with low +computation time and 'better' randomness characteristics. + + + +### FastRand63 - 2^63 periodicity + +**FastRand63** implements a Linear Congruential Generator dedicated to +63-bits calculations with a short period (about 9.2e+18) and very short +time computation. + +LCG models evaluate pseudo-random numbers suites *x(i)* as a simple +mathematical function of *x(i-1)*: + + x(i) = ( a * x(i-1) + c ) mod m + +The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) +since these two values have evaluated to be the 'best' ones for LCGs within +TestU01 while *m* = 2^63. + +Results are nevertheless considered to be poor as stated in the evaluation +done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not +recommended to use such pseudo-random numbers generators for serious +simulation applications, even if FastRandom63 fails on very far less tests +than does FastRandom32. + +See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% +lower computation time. + + + +### MRGRand287 - 2^287 periodicity + +**MRGRand287** implements a fast 32-bits Multiple Recursive Generator (MRG) +with a long period (2^287, i.e. 2.49e+86) and low computation time (about +twice the computation time of above LCGs) but 256 integers memory +consumption. + +Multiple Recursive Generators (MRGs) use recurrence to evaluate +pseudo-random numbers suites. For 2 to more different values of *k*, +recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + +MRGs offer very large periods with the best known results in the evaluation +of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard. It +is therefore strongly recommended to use such pseudo-random numbers +generators rather than LCG ones for serious simulation applications. + +The implementation of this MRG 32-bits model is finally based on a Lagged +Fibonacci generator (LFIB), the Marsa-LFIB4 one. + +Lagged Fibonacci generators *LFib( m, r, k, op)* use the recurrence + + x(i) = ( x(i-r) op (x(i-k) ) mod m + +where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + +With the + or - operation, such generators are true MRGs. They offer very +large periods with the best known results in the evaluation of their +randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in their +paper. + +The Marsa-LIBF4 version, i.e. **MRGRand287** implementation, uses the +recurrence: + + x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 + + + +### MRGRand1457 - 2^1457 periodicity + +**MRGRand1457** implements a fast 31-bits Multiple Recursive Generator with +a longer period than MRGRan287 (2^1457 vs. 2^287, i.e. 4.0e+438 vs. 2.5e+86) +and 80 % more computation time but with much less memory space consumption +(47 vs. 256 integers). + +The implementation of this MRG 31-bits model is based on DX-47-3 +pseudo-random generator proposed by Deng and Lin, see [2]. The DX-47-3 +version uses the recurrence: + + x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) + + + +### MRGRand49507 - 2^49507 periodicity + +**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with +the longer period of all of the PRGs that are implemented if **PyRandLib** +(2^49507, i.e. 1.2e+14903) with low computation time also (same as for +MRGRand287) but use of much more memory space (1597 integers). + +The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' +MRG proposed by Deng, see [3]. It uses the recurrence: + + x(i) = (-2^25-2^7) * ( x(i-7) + x(i-1597) ) mod (2^31-1) + + + +### LFibRand78 - 2^78 periodicity + +**LFibRand78** implements a fast 64-bits Lagged Fibonacci generator (LFib). +Lagged Fibonacci generators *LFib( m, r, k, op)* use the recurrence + + x(i) = ( x(i-r) op (x(i-k) ) mod m + +where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + +With the + or - operation, such generators are MRGs. They offer very large +periods with the best known results in the evaluation of their randomness, +as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard +while offering very low computation times. + +The implementation of **LFibRand78** is based on a Lagged Fibonacci +generator (LFib) which uses the recurrence: + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + +It offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time +due to the use of a 2^64 modulo (less than twice the computation time of +LCGs) and low memory consumption (17 integers). + +Please notice that the TestUO1 article states that the operator should be +'*' while George Marsaglia in its original article [4] used the operator +'+'. We've implemented in **PyRandLib** the original operator '+'. + + + +### LFibRand116 - 2^116 periodicity + +**LFibRand116** implements an LFib 64-bits generator proposed by George +Marsaglia in [4]. This PRG uses the recurrence + + x(i) = ( x(i-24) + x(i-55) ) mod 2^64 + +It offers a period of about 2^116 - i.e. 8.3e+34 - with low computation +time due to the use of a 2^64 modulo (less than twice the computation time +of LCGs) and some memory consumption (55 integers). + +Please notice that the TestUO1 article states that the operator should be +'*' while George Marsaglia in its original article [4] used the operator +'+'. We've implemented in **PyRandLib** the original operator '+'. + + + +### LFibRand668 - 2^668 periodicity + +**LFibRand668** implements an LFib 64-bits generator proposed by George +Marsaglia in [4]. This PRG uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + +It offers a period of about 2^668 - i.e. 1.2e+201 - with low computation +time due to the use of a 2^64 modulo (less than twice the computation time +of LCGs) and much memory consumption (607 integers). + +Please notice that the TestUO1 article states that the operator should be +'*' while George Marsaglia in its original article [4] used the operator +'+'. We've implemented in **PyRandLib** the original operator '+'. + + + +### LFibRand1340 - 2^1340 periodicity + +**LFibRand1340** implements an LFib 64-bits generator proposed by George +Marsaglia in [4]. This PRG uses the recurrence + + x(i) = ( x(i-861) + x(i-1279) ) mod 2^64 + +It offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation +time due to the use of a 2^64 modulo (less than twice the computation time +of LCGs) and much more memory consumption (1279 integers). + +Please notice that the TestUO1 article states that the operator should be +'*' while George Marsaglia in its original article [4] used the operator +'+'. We've implemented in **PyRandLib** the original operator '+'. + + + + +## Inherited Distribution and Generic Functions +(some of next explanation may be free to exact copy of Python 3.6 documentation. +See https://docs.python.org/3.6/library/random.html?highlight=random#module-random) + +Since the base class **BaseRandom** inherits from the built-in class +random.Random, every PRG class of **PyRandLib** gets automatic access to +the next distribution and generic methods: + + +**betavariate**(self, alpha, beta) + +Beta distribution. + +Conditions on the parameters are alpha > 0 and beta > 0. +Returned values range between 0 and 1. + + +**choice**(self, seq) + +Chooses a random element from a non-empty sequence. 'seq' has to be non +empty. + + +**choices**(population, weights=None, *, cum_weights=None, k=1) + +Returns a k sized list of elements chosen from the population with +replacement. If the population is empty, raises IndexError. + +If a weights sequence is specified, selections are made according to the +relative weights. Alternatively, if a cum_weights sequence is given, the +selections are made according to the cumulative weights (perhaps computed +using itertools.accumulate()). For example, the relative weights +[10, 5, 30, 5] are equivalent to the cumulative weights [10, 15, 45, 50]. +Internally, the relative weights are converted to cumulative weights before +making selections, so supplying the cumulative weights saves work. + +If neither weights nor cum_weights are specified, selections are made with +equal probability. If a weights sequence is supplied, it must be the same +length as the population sequence. It is a TypeError to specify both +weights and cum_weights. + +The weights or cum_weights can use any numeric type that interoperates with +the float values returned by random() (that includes integers, floats, and +fractions but excludes decimals). + +Notice: 'choices' has been provided since Python 3.6. It should be +implemented for older versions. + + +**expovariate**(self, lambd) + +Exponential distribution. + +lambd is 1.0 divided by the desired mean. It should be nonzero. (The +parameter should be called "lambda", but this is a reserved word in +Python.) Returned values range from 0 to positive infinity if lambd is +positive, and from negative infinity to 0 if lambd is negative. + + +**gammavariate**(self, alpha, beta) + +Gamma distribution. Not the gamma function! + +Conditions on the parameters are alpha > 0 and beta > 0. + + +**gauss**(self, mu, sigma) + +Gaussian distribution. + +mu is the mean, and sigma is the standard deviation. +This is slightly faster than the normalvariate() function. + +Not thread-safe without a lock around calls. + + +**getrandbits(self, k)** + +Returns a Python integer with k random bits. Inheriting generators may also +provide it as an optional part of their API. When available, getrandbits() +enables randrange() to handle arbitrarily large ranges. + + +**getstate**(self) + +Returns internal state; can be passed to setstate() later. + + +**lognormvariate**(self, mu, sigma) + +Log normal distribution. + +If you take the natural logarithm of this distribution, you'll get a normal +distribution with mean mu and standard deviation sigma. +mu can have any value, and sigma must be greater than zero. + + +**normalvariate**(self, mu, sigma) + +Normal distribution. + +mu is the mean, and sigma is the standard deviation. See method gauss() for +a faster but not thread-safe equivalent. + + +**paretovariate**(self, alpha) + +Pareto distribution. alpha is the shape parameter. + + +**randint**(self, a, b) + +Returns a random integer in range [a, b], including both end points. + + +**randrange**(self, stop) + +**randrange**(self, start, stop=None, step=1) + +Returns a randomly selected element from range(start, stop, step). This is +equivalent to choice( range(start, stop, step) ) without building a range +object. + +The positional argument pattern matches that of range(). Keyword arguments +should not be used because the function may use them in unexpected ways. + + +**sample**(self, population, k) + +Chooses k unique random elements from a population sequence or set. + +Returns a new list containing elements from the population while leaving +the original population unchanged. The resulting list is in selection +order so that all sub-slices will also be valid random samples. This allows +raffle winners (the sample) to be partitioned into grand prize and second +place winners (the subslices). + +Members of the population need not be hashable or unique. If the population +contains repeats, then each occurrence is a possible selection in the +sample. + +To choose a sample in a range of integers, use range as an argument. This +is especially fast and space efficient for sampling from a large +population: sample(range(10000000), 60) + + +**seed**(self, a=None, version=2) + +Initialize internal state from hashable object. + +None or no argument seeds from current time or from an operating system +specific randomness source if available. + +For version 2 (the default), all of the bits are used if *a* is a str, +bytes, or bytearray. For version 1, the hash() of *a* is used instead. + +If *a* is an int, all bits are used. + + +**setstate**(self, state) + +Restores internal state from object returned by getstate(). + + +**shuffle**(self, x, random=None) + +Shuffle the sequence x in place. Returns None. + +The optional argument random is a 0-argument function returning a random +float in [0.0, 1.0); by default, this is the function random(). + +To shuffle an immutable sequence and return a new shuffled list, use +sample(x, k=len(x)) instead. + +Note that even for small len(x), the total number of permutations of x can +quickly grow larger than the period of most random number generators. This +implies that most permutations of a long sequence can never be generated. +For example, a sequence of length 2080 is the largest that can fit within +the period of the Mersenne Twister random number generator. + + +**triangular**(self, low=0.0, high=1.0, mode=None) + +Triangular distribution. + +Continuous distribution bounded by given lower and upper limits, and having +a given mode value in-between. Returns a random floating point number *N* +such that low <= *N* <= high and with the specified mode between those +bounds. The low and high bounds default to zero and one. The mode argument +defaults to the midpoint between the bounds, giving a symmetric +distribution. + +http://en.wikipedia.org/wiki/Triangular_distribution + + +**uniform**(self, a, b) + +Gets a random number in the range [a, b) or [a, b] depending on rounding. + + +**vonmisesvariate**(self, mu, kappa) + +Circular data distribution. + +mu is the mean angle, expressed in radians between 0 and 2*pi, and kappa is +the concentration parameter, which must be greater than or equal to zero. +If kappa is equal to zero, this distribution reduces to a uniform random +angle over the range 0 to 2*pi. + + +**weibullvariate**(self, alpha, beta) + +Weibull distribution. + +alpha is the scale parameter and beta is the shape parameter. + + + +## References + +**[1]** Pierre L'Ecuyer and Richard Simard. 2007. +*TestU01: A C library for empirical testing of random number generators*. +ACM Transaction on Mathematical Software, Vol.33 N.4, Article 22 (August 2007), 40 pages. DOI: http://dx.doi.org/10.1145/1268776.1268777 + +BibTex: +@article{L'Ecuyer:2007:TCL:1268776.1268777, + author = {L'Ecuyer, Pierre and Simard, Richard}, + title = {TestU01: A C Library for Empirical Testing of Random Number Generators}, + journal = {ACM Trans. Math. Softw.}, + issue_date = {August 2007}, + volume = {33}, + number = {4}, + month = aug, + year = {2007}, + issn = {0098-3500}, + pages = {22:1--22:40}, + articleno = {22}, + numpages = {40}, + url = {http://doi.acm.org/10.1145/1268776.1268777}, + doi = {10.1145/1268776.1268777}, + acmid = {1268777}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {Statistical software, random number generators, random number tests, statistical test}, +} + + +**[2]** Lih-Yuan Deng & Dennis K. J. Lin. 2000. +*Random number generation for the new century*. +The American Statistician Vol.54, N.2, pp. 145–150. + +BibTex: +@article{doi:10.1080/00031305.2000.10474528, +author = { Lih-Yuan Deng and Dennis K. J. Lin }, +title = {Random Number Generation for the New Century}, +journal = {The American Statistician}, +volume = {54}, +number = {2}, +pages = {145-150}, +year = {2000}, +doi = {10.1080/00031305.2000.10474528}, +URL = {ttp://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, +eprint = {http://amstat.tandfonline.com/doi/pdf/10.1080/00031305.2000.10474528} +} + + +**[3]** Lih-Yuan Deng. 2005. +*Efficient and portable multiple recursive generators of large order*. +ACM Transactions on Modeling and Computer. Simulation 15:1. + + +**[4]** Georges Marsaglia. 1985. +*A current view of random number generators*. +In Computer Science and Statistics, Sixteenth Symposium on the Interface. Elsevier Science Publishers, North-Holland, +Amsterdam, 1985, The Netherlands. pp. 3–10. + + +**[5]** Makoto Matsumoto and Takuji Nishimura. 1998. +*Mersenne twister: A 623-dimensionally equidistributed uniform pseudo-random number generator.* +In ACM Transactions on Modeling and Computer Simulation (TOMACS) - Special issue on uniform random number generation. +Vol.8 N.1, Jan. 1998, pp. 3-30. diff --git a/PyRandLib/__init__.py b/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib/annotation_types.py b/PyRandLib/annotation_types.py new file mode 100644 index 0000000..e097004 --- /dev/null +++ b/PyRandLib/annotation_types.py @@ -0,0 +1,35 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Tuple, Union + +Numerical = Union[ int, float ] +StatesList = Union[ Tuple[int], List[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py new file mode 100644 index 0000000..40c2aa5 --- /dev/null +++ b/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/PyRandLib/baselcg.py b/PyRandLib/baselcg.py new file mode 100644 index 0000000..acd6ce2 --- /dev/null +++ b/PyRandLib/baselcg.py @@ -0,0 +1,104 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module baselcg.py ======================================== diff --git a/PyRandLib/baselfib64.py b/PyRandLib/baselfib64.py new file mode 100644 index 0000000..96da67d --- /dev/null +++ b/PyRandLib/baselfib64.py @@ -0,0 +1,205 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/PyRandLib/basemelg.py b/PyRandLib/basemelg.py new file mode 100644 index 0000000..e69665b --- /dev/null +++ b/PyRandLib/basemelg.py @@ -0,0 +1,199 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/PyRandLib/basemrg.py b/PyRandLib/basemrg.py new file mode 100644 index 0000000..8059c8e --- /dev/null +++ b/PyRandLib/basemrg.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/PyRandLib/basepcg.py b/PyRandLib/basepcg.py new file mode 100644 index 0000000..8c9a423 --- /dev/null +++ b/PyRandLib/basepcg.py @@ -0,0 +1,117 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module basepcg.py ======================================== diff --git a/PyRandLib/baserandom.py b/PyRandLib/baserandom.py new file mode 100644 index 0000000..12b8598 --- /dev/null +++ b/PyRandLib/baserandom.py @@ -0,0 +1,401 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random +from typing import List, Tuple, Union + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | expovariate(self, lambd) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def seed(self, _seed: SeedStateType = None) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Union[Numerical, + Tuple[Numerical], + List[Numerical]] = 1.0, + times: int = 1 ) -> Union[Numerical, List[Numerical]]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + loMask = (1 << (_bitsCount - _rotCount)) - 1 + hiMask = ((1 << _bitsCount) - 1) ^ loMask + hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) + return ((_value & loMask) << _rotCount) | hiBits + + +#===== end of module baserandom.py ===================================== diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py new file mode 100644 index 0000000..2968237 --- /dev/null +++ b/PyRandLib/basesquares.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/PyRandLib/basewell.py b/PyRandLib/basewell.py new file mode 100644 index 0000000..b7dc2ac --- /dev/null +++ b/PyRandLib/basewell.py @@ -0,0 +1,307 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return y ^ a if x & (1 << t) else y + + #------------------------------------------------------------------------- + @classmethod + def _d(cls, s: int) -> int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + #assert 0 <= w <= 32 + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + @property + def _a1(self): + return 0xda44_2d24 + + @property + def _a2(self): + return 0xd3e4_3ffd + + @property + def _a3(self): + return 0x8bdc_b91e + + @property + def _a4(self): + return 0x86a9_d87e + + @property + def _a5(self): + return 0xa8c2_96d1 + + @property + def _a6(self): + return 0x5d6b_45cc + + @property + def _a7(self): + return 0xb729_fcec + +#===== end of module basewell.py ======================================= diff --git a/PyRandLib/basexoroshiro.py b/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..1226391 --- /dev/null +++ b/PyRandLib/basexoroshiro.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: int = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> List[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/PyRandLib/cwg128.py b/PyRandLib/cwg128.py new file mode 100644 index 0000000..22fbab2 --- /dev/null +++ b/PyRandLib/cwg128.py @@ -0,0 +1,159 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: int = (1 << 128) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + +#===== end of module cwg128.py ========================================= diff --git a/PyRandLib/cwg128_64.py b/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..5f7a936 --- /dev/null +++ b/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py new file mode 100644 index 0000000..7b34c77 --- /dev/null +++ b/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/PyRandLib/fastrand32.py b/PyRandLib/fastrand32.py new file mode 100644 index 0000000..9813c66 --- /dev/null +++ b/PyRandLib/fastrand32.py @@ -0,0 +1,122 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix32( _state ) + self._state = initRand() + else: + initRand = SplitMix32() + self._state = initRand() + +#===== end of module fastrand32.py ===================================== diff --git a/PyRandLib/fastrand63.py b/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4483c40 --- /dev/null +++ b/PyRandLib/fastrand63.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix63( _state ) + self._state = initRand() + else: + initRand = SplitMix63() + self._state = initRand() + + +#===== end of module fastrand63.py ===================================== diff --git a/PyRandLib/lfib116.py b/PyRandLib/lfib116.py new file mode 100644 index 0000000..8f1f000 --- /dev/null +++ b/PyRandLib/lfib116.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k24 = self._index-24 + if k24 < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + +#===== end of module lfib116.py ======================================== diff --git a/PyRandLib/lfib1340.py b/PyRandLib/lfib1340.py new file mode 100644 index 0000000..5007045 --- /dev/null +++ b/PyRandLib/lfib1340.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + k861 = self._index-861 + if k861 < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + +#===== end of module lfib1340.py ====================================== diff --git a/PyRandLib/lfib668.py b/PyRandLib/lfib668.py new file mode 100644 index 0000000..0f5e494 --- /dev/null +++ b/PyRandLib/lfib668.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + k273 = self._index-273 + if k273 < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + +#===== end of module lfib668.py ======================================= diff --git a/PyRandLib/lfib78.py b/PyRandLib/lfib78.py new file mode 100644 index 0000000..7f3589c --- /dev/null +++ b/PyRandLib/lfib78.py @@ -0,0 +1,132 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k5 = self._index-5 + if k5 < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib78._STATE_SIZE + + return myValue + +#===== end of module lfib78.py ========================================= diff --git a/PyRandLib/melg19937.py b/PyRandLib/melg19937.py new file mode 100644 index 0000000..5d9b093 --- /dev/null +++ b/PyRandLib/melg19937.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 311 + self._index = i_1 + + s311 = self._state[311] + + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) + self._state[311] = s311 + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg44497.py b/PyRandLib/melg44497.py new file mode 100644 index 0000000..286cdd7 --- /dev/null +++ b/PyRandLib/melg44497.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 695 + self._index = i_1 + + s695 = self._state[695] + + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) + self._state[695] = s695 + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg607.py b/PyRandLib/melg607.py new file mode 100644 index 0000000..6a38a55 --- /dev/null +++ b/PyRandLib/melg607.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 9 + self._index = i_1 + + s9 = self._state[9] + + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) # M=5, s1=13 + self._state[9] = s9 + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) # s2=35 + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) # s3=30, L=3, b = 0x66ed... + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib/mrg1457.py b/PyRandLib/mrg1457.py new file mode 100644 index 0000000..17f8c32 --- /dev/null +++ b/PyRandLib/mrg1457.py @@ -0,0 +1,148 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + k1 = self._index-1 + if k1 < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + k24 = self._index-24 + if k24 < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib/mrg287.py b/PyRandLib/mrg287.py new file mode 100644 index 0000000..119c3f0 --- /dev/null +++ b/PyRandLib/mrg287.py @@ -0,0 +1,151 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + k55 = self._index-55 + if k55 < 0: + k55 += Mrg287._STATE_SIZE + + k119 = self._index-119 + if k119 < 0: + k119 += Mrg287._STATE_SIZE + + k179 = self._index-179 + if k179 < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % self._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib/mrg49507.py b/PyRandLib/mrg49507.py new file mode 100644 index 0000000..2eb2597 --- /dev/null +++ b/PyRandLib/mrg49507.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + k7 = self._index-7 + if k7 < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrgrand49507.py =================================== diff --git a/PyRandLib/mrgrand1457.py b/PyRandLib/mrgrand1457.py new file mode 100644 index 0000000..45ba0c4 --- /dev/null +++ b/PyRandLib/mrgrand1457.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class MRGRand1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See MRGRand49507 for a far longer period (2^49_507, i.e. 1.2e+14_903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = MRGRand1457() + print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) + print( rand(a,b) ) # prints a uniform pseudo-random value within [a , b) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = MRGRand1457() + print( int(diceRoll(1, 7)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRG + should definitively pass. + """ + + + #------------------------------------------------------------------------= + # 'protected' constant + _LIST_SIZE = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------= + def random(self) -> float: + """This is the core of the pseudo-random generator. + + Returned values are within [0.0, 1.0). + """ + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + k1 = self._index-1 + if k1 < 0: + k1 = MRGRand1457._LIST_SIZE - 1 + + k24 = self._index-24 + if k24 < 0: + k24 += MRGRand1457._LIST_SIZE + + # then evaluates current value + myValue = (67633152 * (self._list[k1] + self._list[k24] + self._list[self._index]) ) % 2_147_483_647 + self._list[self._index] = myValue + + # next index + self._index = (self._index + 1) % MRGRand1457._LIST_SIZE + + # then returns float value within [0.0, 1.0) + return myValue / 2_147_483_647.0 + + +#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib/mrgrand287.py b/PyRandLib/mrgrand287.py new file mode 100644 index 0000000..8c07a4f --- /dev/null +++ b/PyRandLib/mrgrand287.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class MRGRand287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + This module is part of library PyRandLib. + + Copyright (c) 2016-2021 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and + longer computation time (2^31-1 modulus calculations) but less memory space + consumption (47 integers). + See MRGRand49507 for a far longer period (2^49_507, i.e. 1.2e+14_903) with low + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore this class is callable: + rand = MRGRand287() + print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) + print( rand(a,b) ) # prints a uniform pseudo-random value within [a , b) + + Notice that for simulating the roll of a dice you should program: + diceRoll = MRGRand287() + print( int(diceRoll(1, 7)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRG + should definitively pass. + """ + + #------------------------------------------------------------------------= + # 'protected' constant + _LIST_SIZE = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO = 4_294_967_295 # i.e. 0xffff_ffff, or (1<<32)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------= + def random(self) -> float: + """This is the core of the pseudo-random generator. + + Returned values are within [0.0, 1.0). + """ + #The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + k55 = self._index-55 + if k55 < 0: + k55 += MRGRand287._LIST_SIZE + + k119 = self._index-119 + if k119 < 0: + k119 += MRGRand287._LIST_SIZE + + k179 = self._index-179 + if k179 < 0: + k179 += MRGRand287._LIST_SIZE + + # then evaluates current value + myValue = (self._list[k55] + self._list[k119] + self._list[k179] + self._list[self._index]) % 4_294_967_295 + self._list[self._index] = myValue + + # next index + self._index = (self._index+1) % self._LIST_SIZE + + # then returns float value within [0.0, 1.0) + return myValue / 4_294_967_295.0 + +#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib/mrgrand49507.py b/PyRandLib/mrgrand49507.py new file mode 100644 index 0000000..2f56fc9 --- /dev/null +++ b/PyRandLib/mrgrand49507.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#----------------------------------------------------------------------------= +from .basemrg import BaseMRG + + +#----------------------------------------------------------------------------= +class MRGRand49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and + longer computation time (2^31-1 modulus calculations) but less memory space + consumption (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = MRGRand49507() + print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) + print( rand(a,b) ) # prints a uniform pseudo-random value within [a , b) + + Notice that for simulating the roll of a dice you should program: + diceRoll = MRGRand49507() + print( int(diceRoll(1, 7)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRG + should definitively pass. + """ + + #------------------------------------------------------------------------= + # 'protected' constant + _LIST_SIZE = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + + #------------------------------------------------------------------------= + def random(self) -> float: + """This is the core of the pseudo-random generator. + + Returned values are within [0.0, 1.0). + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + k7 = self._index-7 + if k7 < 0: + k7 += MRGRand49507._LIST_SIZE + + # then evaluates current value + myValue = (-67108992 * (self._list[k7] + self._list[self._index])) % 2_147_483_647 + self._list[self._index] = myValue + + # next index + self._index = (self._index+1) % MRGRand49507._LIST_SIZE + + # then returns float value within [0.0, 1.0) + return myValue / 2_147_483_647.0 + +#----= end of module mrgrand49507.py ----------------------------------= diff --git a/PyRandLib/pcg1024_32.py b/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..c721499 --- /dev/null +++ b/PyRandLib/pcg1024_32.py @@ -0,0 +1,280 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _EXTENDED_STATE_SIZE: int = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == self._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + result ^= result >> 22 + + self._extendedState[i] = result + + return result == state & 0b11 + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/PyRandLib/pcg128_64.py b/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..6f9eb84 --- /dev/null +++ b/PyRandLib/pcg128_64.py @@ -0,0 +1,176 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO_128 : int = (1 << 128) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & self._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + current_state ^= current_state >> 64 # fixed shift XOR is then evaluated + value = current_state & 0xffff_ffff_ffff_ffff + rot_mask = (1 << random_rotation) - 1 + return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & self._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & self._MODULO_128 + else: + self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + +#===== end of module pcg128_64.py ====================================== diff --git a/PyRandLib/pcg64_32.py b/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..1fa867e --- /dev/null +++ b/PyRandLib/pcg64_32.py @@ -0,0 +1,155 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + current_state ^= current_state >> 22 # fixed shift XOR is then evaluated + return (current_state >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = initRand() + +#===== end of module pcg64_32.py ======================================= diff --git a/PyRandLib/splitmix.py b/PyRandLib/splitmix.py new file mode 100644 index 0000000..2d674e8 --- /dev/null +++ b/PyRandLib/splitmix.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 diff --git a/PyRandLib/squares32.py b/PyRandLib/squares32.py new file mode 100644 index 0000000..f916093 --- /dev/null +++ b/PyRandLib/squares32.py @@ -0,0 +1,110 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + +#===== end of module squares32.py ====================================== diff --git a/PyRandLib/squares64.py b/PyRandLib/squares64.py new file mode 100644 index 0000000..573f2e2 --- /dev/null +++ b/PyRandLib/squares64.py @@ -0,0 +1,132 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + +#===== end of module squares64.py ====================================== diff --git a/PyRandLib/types.py b/PyRandLib/types.py new file mode 100644 index 0000000..cabf22b --- /dev/null +++ b/PyRandLib/types.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Copyright (c) 2021-2022 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Tuple, Union + +Numerical = Union[ int, float ] +StateType = Union[ Tuple[Numerical], List[Numerical], Tuple[List[Numerical], int] ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.types =====# diff --git a/PyRandLib/well1024a.py b/PyRandLib/well1024a.py new file mode 100644 index 0000000..9f92c62 --- /dev/null +++ b/PyRandLib/well1024a.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + +#===== end of module well1024a.py ====================================== diff --git a/PyRandLib/well19937c.py b/PyRandLib/well19937c.py new file mode 100644 index 0000000..7b836e0 --- /dev/null +++ b/PyRandLib/well19937c.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 623 + else: + i_1, i_2 = 623, 622 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) + z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) + self._index = i_1 + + return self._tempering(z3, 0xe46e1700, 0x9b868000) + +#===== end of module well19937c.py ===================================== diff --git a/PyRandLib/well44497b.py b/PyRandLib/well44497b.py new file mode 100644 index 0000000..0157139 --- /dev/null +++ b/PyRandLib/well44497b.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 1390 + else: + i_1, i_2 = 1390, 1389 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 + self._index = i_1 + + return self._tempering(z3, 0x93dd1400, 0xfa118000) + +#===== end of module Well44497b.py ===================================== diff --git a/PyRandLib/well512a.py b/PyRandLib/well512a.py new file mode 100644 index 0000000..42b14a0 --- /dev/null +++ b/PyRandLib/well512a.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + +#===== end of module well512a.py ======================================= diff --git a/PyRandLib/xoroshiro1024.py b/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..62d5cb6 --- /dev/null +++ b/PyRandLib/xoroshiro1024.py @@ -0,0 +1,204 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + #------------------------------------------------------------------------- + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + ''' + const int q = p; + const uint64_t s0 = s[p = (p + 1) & 15]; + uint64_t s15 = s[q]; + const uint64_t result_plus = s0 + s15; + const uint64_t result_plusplus = rotl(s0 + s15, R) + s15; + const uint64_t result_star = s0 * S; + const uint64_t result_starstar = rotl(s0 * S, R) * T; + s15 ^= s0; + s[q] = rotl(s0, A) ^ s15 ^ (s15 << B); + s[p] = rotl(s15, C); + ''' + previousIndex = self._index + # advances the internal state of the PRNG + self._index += 1 + self._index &= self._SIZE_MODULO + sLow = self._state[ self._index ] + sHigh = self._state[ previousIndex ] ^ sLow + self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) + self._state[ self._index ] = self._rotleft( sHigh, 36 ) + # returns the output value + return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & self._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/PyRandLib/xoroshiro256.py b/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..0b09b63 --- /dev/null +++ b/PyRandLib/xoroshiro256.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & self._MODULO + self._s3 = self._rotleft( self._s3, 45 ) + # returns the output value + return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/PyRandLib/xoroshiro512.py b/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..73ed1c8 --- /dev/null +++ b/PyRandLib/xoroshiro512.py @@ -0,0 +1,178 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & self._MODULO + self._state[7] = self._rotleft( self._state[7], 21 ) + # returns the output value + return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(8) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.10/PyRandLib/__init__.py b/Python3.10/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.10/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/Python3.10/PyRandLib/annotation_types.py b/Python3.10/PyRandLib/annotation_types.py new file mode 100644 index 0000000..d5680eb --- /dev/null +++ b/Python3.10/PyRandLib/annotation_types.py @@ -0,0 +1,33 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +Numerical = int | float +StatesList = tuple[int] | list[int] +StatesListAndExt = tuple[StatesList, int] +StateType = StatesList | StatesListAndExt +SeedStateType = Numerical | StateType + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.10/PyRandLib/basecwg.py b/Python3.10/PyRandLib/basecwg.py new file mode 100644 index 0000000..d8b51bf --- /dev/null +++ b/Python3.10/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.10/PyRandLib/baselcg.py b/Python3.10/PyRandLib/baselcg.py new file mode 100644 index 0000000..455c230 --- /dev/null +++ b/Python3.10/PyRandLib/baselcg.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.10/PyRandLib/baselfib64.py b/Python3.10/PyRandLib/baselfib64.py new file mode 100644 index 0000000..d1ef6c7 --- /dev/null +++ b/Python3.10/PyRandLib/baselfib64.py @@ -0,0 +1,206 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.10/PyRandLib/basemelg.py b/Python3.10/PyRandLib/basemelg.py new file mode 100644 index 0000000..5b3faf2 --- /dev/null +++ b/Python3.10/PyRandLib/basemelg.py @@ -0,0 +1,200 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.10/PyRandLib/basemrg.py b/Python3.10/PyRandLib/basemrg.py new file mode 100644 index 0000000..5e05b15 --- /dev/null +++ b/Python3.10/PyRandLib/basemrg.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.10/PyRandLib/basepcg.py b/Python3.10/PyRandLib/basepcg.py new file mode 100644 index 0000000..066e7bb --- /dev/null +++ b/Python3.10/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.10/PyRandLib/basesquares.py b/Python3.10/PyRandLib/basesquares.py new file mode 100644 index 0000000..47017c6 --- /dev/null +++ b/Python3.10/PyRandLib/basesquares.py @@ -0,0 +1,161 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.10/PyRandLib/basewell.py b/Python3.10/PyRandLib/basewell.py new file mode 100644 index 0000000..53e052c --- /dev/null +++ b/Python3.10/PyRandLib/basewell.py @@ -0,0 +1,289 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.10/PyRandLib/basexoroshiro.py b/Python3.10/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..81a7514 --- /dev/null +++ b/Python3.10/PyRandLib/basexoroshiro.py @@ -0,0 +1,189 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.10/PyRandLib/cwg128.py b/Python3.10/PyRandLib/cwg128.py new file mode 100644 index 0000000..f5350bc --- /dev/null +++ b/Python3.10/PyRandLib/cwg128.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.10/PyRandLib/cwg128_64.py b/Python3.10/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..67ff0e2 --- /dev/null +++ b/Python3.10/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.10/PyRandLib/cwg64.py b/Python3.10/PyRandLib/cwg64.py new file mode 100644 index 0000000..1ed40b8 --- /dev/null +++ b/Python3.10/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int | float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.10/PyRandLib/fastrand32.py b/Python3.10/PyRandLib/fastrand32.py new file mode 100644 index 0000000..26a25c1 --- /dev/null +++ b/Python3.10/PyRandLib/fastrand32.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.10/PyRandLib/fastrand63.py b/Python3.10/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4e10144 --- /dev/null +++ b/Python3.10/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.10/PyRandLib/lfib116.py b/Python3.10/PyRandLib/lfib116.py new file mode 100644 index 0000000..5083c07 --- /dev/null +++ b/Python3.10/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.10/PyRandLib/lfib1340.py b/Python3.10/PyRandLib/lfib1340.py new file mode 100644 index 0000000..0f4ecdf --- /dev/null +++ b/Python3.10/PyRandLib/lfib1340.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.10/PyRandLib/lfib668.py b/Python3.10/PyRandLib/lfib668.py new file mode 100644 index 0000000..3ae6989 --- /dev/null +++ b/Python3.10/PyRandLib/lfib668.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.10/PyRandLib/lfib78.py b/Python3.10/PyRandLib/lfib78.py new file mode 100644 index 0000000..f2cdeda --- /dev/null +++ b/Python3.10/PyRandLib/lfib78.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.10/PyRandLib/melg19937.py b/Python3.10/PyRandLib/melg19937.py new file mode 100644 index 0000000..10a3332 --- /dev/null +++ b/Python3.10/PyRandLib/melg19937.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.10/PyRandLib/melg44497.py b/Python3.10/PyRandLib/melg44497.py new file mode 100644 index 0000000..5f14081 --- /dev/null +++ b/Python3.10/PyRandLib/melg44497.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.10/PyRandLib/melg607.py b/Python3.10/PyRandLib/melg607.py new file mode 100644 index 0000000..d6c10b6 --- /dev/null +++ b/Python3.10/PyRandLib/melg607.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.10/PyRandLib/mrg1457.py b/Python3.10/PyRandLib/mrg1457.py new file mode 100644 index 0000000..007b8fd --- /dev/null +++ b/Python3.10/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.10/PyRandLib/mrg287.py b/Python3.10/PyRandLib/mrg287.py new file mode 100644 index 0000000..093406d --- /dev/null +++ b/Python3.10/PyRandLib/mrg287.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.10/PyRandLib/mrg49507.py b/Python3.10/PyRandLib/mrg49507.py new file mode 100644 index 0000000..a196c65 --- /dev/null +++ b/Python3.10/PyRandLib/mrg49507.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.10/PyRandLib/pcg1024_32.py b/Python3.10/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..586b6ad --- /dev/null +++ b/Python3.10/PyRandLib/pcg1024_32.py @@ -0,0 +1,281 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState ) + + case 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + case _: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.10/PyRandLib/pcg128_64.py b/Python3.10/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..ddef70e --- /dev/null +++ b/Python3.10/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.10/PyRandLib/pcg64_32.py b/Python3.10/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..4eed457 --- /dev/null +++ b/Python3.10/PyRandLib/pcg64_32.py @@ -0,0 +1,154 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.10/PyRandLib/splitmix.py b/Python3.10/PyRandLib/splitmix.py new file mode 100644 index 0000000..0b97a51 --- /dev/null +++ b/Python3.10/PyRandLib/splitmix.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.10/PyRandLib/squares32.py b/Python3.10/PyRandLib/squares32.py new file mode 100644 index 0000000..268b56e --- /dev/null +++ b/Python3.10/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.10/PyRandLib/squares64.py b/Python3.10/PyRandLib/squares64.py new file mode 100644 index 0000000..5efd5b5 --- /dev/null +++ b/Python3.10/PyRandLib/squares64.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.10/PyRandLib/well1024a.py b/Python3.10/PyRandLib/well1024a.py new file mode 100644 index 0000000..dfd7085 --- /dev/null +++ b/Python3.10/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.10/PyRandLib/well19937c.py b/Python3.10/PyRandLib/well19937c.py new file mode 100644 index 0000000..75ccee7 --- /dev/null +++ b/Python3.10/PyRandLib/well19937c.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 623, 622 + + case 1: + i_1, i_2 = 0, 623 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.10/PyRandLib/well44497b.py b/Python3.10/PyRandLib/well44497b.py new file mode 100644 index 0000000..9a14931 --- /dev/null +++ b/Python3.10/PyRandLib/well44497b.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 1390, 1389 + + case 1: + i_1, i_2 = 0, 1390 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.10/PyRandLib/well512a.py b/Python3.10/PyRandLib/well512a.py new file mode 100644 index 0000000..aaf532e --- /dev/null +++ b/Python3.10/PyRandLib/well512a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.10/PyRandLib/xoroshiro1024.py b/Python3.10/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..9a0f7a1 --- /dev/null +++ b/Python3.10/PyRandLib/xoroshiro1024.py @@ -0,0 +1,191 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.10/PyRandLib/xoroshiro256.py b/Python3.10/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..631b301 --- /dev/null +++ b/Python3.10/PyRandLib/xoroshiro256.py @@ -0,0 +1,180 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.10/PyRandLib/xoroshiro512.py b/Python3.10/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..10d3c0a --- /dev/null +++ b/Python3.10/PyRandLib/xoroshiro512.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.10/testCPUPerfs.py b/Python3.10/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.10/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.10/testED.py b/Python3.10/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.10/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= diff --git a/Python3.11/PyRandLib/__init__.py b/Python3.11/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.11/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/Python3.11/PyRandLib/annotation_types.py b/Python3.11/PyRandLib/annotation_types.py new file mode 100644 index 0000000..d5680eb --- /dev/null +++ b/Python3.11/PyRandLib/annotation_types.py @@ -0,0 +1,33 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +Numerical = int | float +StatesList = tuple[int] | list[int] +StatesListAndExt = tuple[StatesList, int] +StateType = StatesList | StatesListAndExt +SeedStateType = Numerical | StateType + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.11/PyRandLib/basecwg.py b/Python3.11/PyRandLib/basecwg.py new file mode 100644 index 0000000..d8b51bf --- /dev/null +++ b/Python3.11/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.11/PyRandLib/baselcg.py b/Python3.11/PyRandLib/baselcg.py new file mode 100644 index 0000000..455c230 --- /dev/null +++ b/Python3.11/PyRandLib/baselcg.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.11/PyRandLib/baselfib64.py b/Python3.11/PyRandLib/baselfib64.py new file mode 100644 index 0000000..d1ef6c7 --- /dev/null +++ b/Python3.11/PyRandLib/baselfib64.py @@ -0,0 +1,206 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.11/PyRandLib/basemelg.py b/Python3.11/PyRandLib/basemelg.py new file mode 100644 index 0000000..5b3faf2 --- /dev/null +++ b/Python3.11/PyRandLib/basemelg.py @@ -0,0 +1,200 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.11/PyRandLib/basemrg.py b/Python3.11/PyRandLib/basemrg.py new file mode 100644 index 0000000..5e05b15 --- /dev/null +++ b/Python3.11/PyRandLib/basemrg.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.11/PyRandLib/basepcg.py b/Python3.11/PyRandLib/basepcg.py new file mode 100644 index 0000000..066e7bb --- /dev/null +++ b/Python3.11/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.11/PyRandLib/basesquares.py b/Python3.11/PyRandLib/basesquares.py new file mode 100644 index 0000000..47017c6 --- /dev/null +++ b/Python3.11/PyRandLib/basesquares.py @@ -0,0 +1,161 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.11/PyRandLib/basewell.py b/Python3.11/PyRandLib/basewell.py new file mode 100644 index 0000000..53e052c --- /dev/null +++ b/Python3.11/PyRandLib/basewell.py @@ -0,0 +1,289 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.11/PyRandLib/basexoroshiro.py b/Python3.11/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..81a7514 --- /dev/null +++ b/Python3.11/PyRandLib/basexoroshiro.py @@ -0,0 +1,189 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.11/PyRandLib/cwg128.py b/Python3.11/PyRandLib/cwg128.py new file mode 100644 index 0000000..f5350bc --- /dev/null +++ b/Python3.11/PyRandLib/cwg128.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.11/PyRandLib/cwg128_64.py b/Python3.11/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..67ff0e2 --- /dev/null +++ b/Python3.11/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.11/PyRandLib/cwg64.py b/Python3.11/PyRandLib/cwg64.py new file mode 100644 index 0000000..1ed40b8 --- /dev/null +++ b/Python3.11/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int | float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.11/PyRandLib/fastrand32.py b/Python3.11/PyRandLib/fastrand32.py new file mode 100644 index 0000000..26a25c1 --- /dev/null +++ b/Python3.11/PyRandLib/fastrand32.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.11/PyRandLib/fastrand63.py b/Python3.11/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4e10144 --- /dev/null +++ b/Python3.11/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.11/PyRandLib/lfib116.py b/Python3.11/PyRandLib/lfib116.py new file mode 100644 index 0000000..5083c07 --- /dev/null +++ b/Python3.11/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.11/PyRandLib/lfib1340.py b/Python3.11/PyRandLib/lfib1340.py new file mode 100644 index 0000000..0f4ecdf --- /dev/null +++ b/Python3.11/PyRandLib/lfib1340.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.11/PyRandLib/lfib668.py b/Python3.11/PyRandLib/lfib668.py new file mode 100644 index 0000000..3ae6989 --- /dev/null +++ b/Python3.11/PyRandLib/lfib668.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.11/PyRandLib/lfib78.py b/Python3.11/PyRandLib/lfib78.py new file mode 100644 index 0000000..f2cdeda --- /dev/null +++ b/Python3.11/PyRandLib/lfib78.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.11/PyRandLib/melg19937.py b/Python3.11/PyRandLib/melg19937.py new file mode 100644 index 0000000..10a3332 --- /dev/null +++ b/Python3.11/PyRandLib/melg19937.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.11/PyRandLib/melg44497.py b/Python3.11/PyRandLib/melg44497.py new file mode 100644 index 0000000..5f14081 --- /dev/null +++ b/Python3.11/PyRandLib/melg44497.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.11/PyRandLib/melg607.py b/Python3.11/PyRandLib/melg607.py new file mode 100644 index 0000000..d6c10b6 --- /dev/null +++ b/Python3.11/PyRandLib/melg607.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.11/PyRandLib/mrg1457.py b/Python3.11/PyRandLib/mrg1457.py new file mode 100644 index 0000000..007b8fd --- /dev/null +++ b/Python3.11/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.11/PyRandLib/mrg287.py b/Python3.11/PyRandLib/mrg287.py new file mode 100644 index 0000000..093406d --- /dev/null +++ b/Python3.11/PyRandLib/mrg287.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.11/PyRandLib/mrg49507.py b/Python3.11/PyRandLib/mrg49507.py new file mode 100644 index 0000000..a196c65 --- /dev/null +++ b/Python3.11/PyRandLib/mrg49507.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.11/PyRandLib/pcg1024_32.py b/Python3.11/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..586b6ad --- /dev/null +++ b/Python3.11/PyRandLib/pcg1024_32.py @@ -0,0 +1,281 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState ) + + case 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + case _: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.11/PyRandLib/pcg128_64.py b/Python3.11/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..ddef70e --- /dev/null +++ b/Python3.11/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.11/PyRandLib/pcg64_32.py b/Python3.11/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..4eed457 --- /dev/null +++ b/Python3.11/PyRandLib/pcg64_32.py @@ -0,0 +1,154 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.11/PyRandLib/splitmix.py b/Python3.11/PyRandLib/splitmix.py new file mode 100644 index 0000000..0b97a51 --- /dev/null +++ b/Python3.11/PyRandLib/splitmix.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.11/PyRandLib/squares32.py b/Python3.11/PyRandLib/squares32.py new file mode 100644 index 0000000..268b56e --- /dev/null +++ b/Python3.11/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.11/PyRandLib/squares64.py b/Python3.11/PyRandLib/squares64.py new file mode 100644 index 0000000..5efd5b5 --- /dev/null +++ b/Python3.11/PyRandLib/squares64.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.11/PyRandLib/well1024a.py b/Python3.11/PyRandLib/well1024a.py new file mode 100644 index 0000000..dfd7085 --- /dev/null +++ b/Python3.11/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.11/PyRandLib/well19937c.py b/Python3.11/PyRandLib/well19937c.py new file mode 100644 index 0000000..75ccee7 --- /dev/null +++ b/Python3.11/PyRandLib/well19937c.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 623, 622 + + case 1: + i_1, i_2 = 0, 623 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.11/PyRandLib/well44497b.py b/Python3.11/PyRandLib/well44497b.py new file mode 100644 index 0000000..9a14931 --- /dev/null +++ b/Python3.11/PyRandLib/well44497b.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 1390, 1389 + + case 1: + i_1, i_2 = 0, 1390 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.11/PyRandLib/well512a.py b/Python3.11/PyRandLib/well512a.py new file mode 100644 index 0000000..aaf532e --- /dev/null +++ b/Python3.11/PyRandLib/well512a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.11/PyRandLib/xoroshiro1024.py b/Python3.11/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..9a0f7a1 --- /dev/null +++ b/Python3.11/PyRandLib/xoroshiro1024.py @@ -0,0 +1,191 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.11/PyRandLib/xoroshiro256.py b/Python3.11/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..631b301 --- /dev/null +++ b/Python3.11/PyRandLib/xoroshiro256.py @@ -0,0 +1,180 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.11/PyRandLib/xoroshiro512.py b/Python3.11/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..10d3c0a --- /dev/null +++ b/Python3.11/PyRandLib/xoroshiro512.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.11/testCPUPerfs.py b/Python3.11/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.11/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.11/testED.py b/Python3.11/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.11/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= diff --git a/Python3.6/PyRandLib/__init__.py b/Python3.6/PyRandLib/__init__.py new file mode 100644 index 0000000..d5ac68a --- /dev/null +++ b/Python3.6/PyRandLib/__init__.py @@ -0,0 +1,47 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 + + +#===== end of package module __init__.py =============================== diff --git a/Python3.6/PyRandLib/annotation_types.py b/Python3.6/PyRandLib/annotation_types.py new file mode 100644 index 0000000..e097004 --- /dev/null +++ b/Python3.6/PyRandLib/annotation_types.py @@ -0,0 +1,35 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Tuple, Union + +Numerical = Union[ int, float ] +StatesList = Union[ Tuple[int], List[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.6/PyRandLib/basecwg.py b/Python3.6/PyRandLib/basecwg.py new file mode 100644 index 0000000..40c2aa5 --- /dev/null +++ b/Python3.6/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.6/PyRandLib/baselcg.py b/Python3.6/PyRandLib/baselcg.py new file mode 100644 index 0000000..acd6ce2 --- /dev/null +++ b/Python3.6/PyRandLib/baselcg.py @@ -0,0 +1,104 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module baselcg.py ======================================== diff --git a/Python3.6/PyRandLib/baselfib64.py b/Python3.6/PyRandLib/baselfib64.py new file mode 100644 index 0000000..96da67d --- /dev/null +++ b/Python3.6/PyRandLib/baselfib64.py @@ -0,0 +1,205 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.6/PyRandLib/basemelg.py b/Python3.6/PyRandLib/basemelg.py new file mode 100644 index 0000000..e69665b --- /dev/null +++ b/Python3.6/PyRandLib/basemelg.py @@ -0,0 +1,199 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.6/PyRandLib/basemrg.py b/Python3.6/PyRandLib/basemrg.py new file mode 100644 index 0000000..8059c8e --- /dev/null +++ b/Python3.6/PyRandLib/basemrg.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.6/PyRandLib/basepcg.py b/Python3.6/PyRandLib/basepcg.py new file mode 100644 index 0000000..3cd35f8 --- /dev/null +++ b/Python3.6/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.6/PyRandLib/basesquares.py b/Python3.6/PyRandLib/basesquares.py new file mode 100644 index 0000000..2968237 --- /dev/null +++ b/Python3.6/PyRandLib/basesquares.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.6/PyRandLib/basewell.py b/Python3.6/PyRandLib/basewell.py new file mode 100644 index 0000000..c040bd5 --- /dev/null +++ b/Python3.6/PyRandLib/basewell.py @@ -0,0 +1,288 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: int = 0xda44_2d24 + _a2: int = 0xd3e4_3ffd + _a3: int = 0x8bdc_b91e + _a4: int = 0x86a9_d87e + _a5: int = 0xa8c2_96d1 + _a6: int = 0x5d6b_45cc + _a7: int = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.6/PyRandLib/basexoroshiro.py b/Python3.6/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..447c256 --- /dev/null +++ b/Python3.6/PyRandLib/basexoroshiro.py @@ -0,0 +1,188 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO: int = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> List[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.6/PyRandLib/cwg128.py b/Python3.6/PyRandLib/cwg128.py new file mode 100644 index 0000000..faff1c7 --- /dev/null +++ b/Python3.6/PyRandLib/cwg128.py @@ -0,0 +1,158 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO: int = (1 << 128) - 1 # Notice: optimization on modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.6/PyRandLib/cwg128_64.py b/Python3.6/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..bd0fbe1 --- /dev/null +++ b/Python3.6/PyRandLib/cwg128_64.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.6/PyRandLib/cwg64.py b/Python3.6/PyRandLib/cwg64.py new file mode 100644 index 0000000..3c1a931 --- /dev/null +++ b/Python3.6/PyRandLib/cwg64.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.6/PyRandLib/fastrand32.py b/Python3.6/PyRandLib/fastrand32.py new file mode 100644 index 0000000..9fa1d0e --- /dev/null +++ b/Python3.6/PyRandLib/fastrand32.py @@ -0,0 +1,123 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix32( _state ) + self._state = initRand() + else: + initRand = SplitMix32() + self._state = initRand() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.6/PyRandLib/fastrand63.py b/Python3.6/PyRandLib/fastrand63.py new file mode 100644 index 0000000..9d8f78b --- /dev/null +++ b/Python3.6/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix63( _state ) + self._state = initRand() + else: + initRand = SplitMix63() + self._state = initRand() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.6/PyRandLib/lfib116.py b/Python3.6/PyRandLib/lfib116.py new file mode 100644 index 0000000..c0b3e63 --- /dev/null +++ b/Python3.6/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k24 = self._index-24 + if k24 < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.6/PyRandLib/lfib1340.py b/Python3.6/PyRandLib/lfib1340.py new file mode 100644 index 0000000..cc0569e --- /dev/null +++ b/Python3.6/PyRandLib/lfib1340.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + k861 = self._index-861 + if k861 < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.6/PyRandLib/lfib668.py b/Python3.6/PyRandLib/lfib668.py new file mode 100644 index 0000000..fa60d74 --- /dev/null +++ b/Python3.6/PyRandLib/lfib668.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + k273 = self._index-273 + if k273 < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.6/PyRandLib/lfib78.py b/Python3.6/PyRandLib/lfib78.py new file mode 100644 index 0000000..a50d034 --- /dev/null +++ b/Python3.6/PyRandLib/lfib78.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k5 = self._index-5 + if k5 < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.6/PyRandLib/melg19937.py b/Python3.6/PyRandLib/melg19937.py new file mode 100644 index 0000000..49aa010 --- /dev/null +++ b/Python3.6/PyRandLib/melg19937.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 311 + self._index = i_1 + + s311 = self._state[311] + + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) + self._state[311] = s311 + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py diff --git a/Python3.6/PyRandLib/melg44497.py b/Python3.6/PyRandLib/melg44497.py new file mode 100644 index 0000000..ef2a706 --- /dev/null +++ b/Python3.6/PyRandLib/melg44497.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 695 + self._index = i_1 + + s695 = self._state[695] + + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) + self._state[695] = s695 + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.6/PyRandLib/melg607.py b/Python3.6/PyRandLib/melg607.py new file mode 100644 index 0000000..c58f1f7 --- /dev/null +++ b/Python3.6/PyRandLib/melg607.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # Notice: this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 9 + self._index = i_1 + + s9 = self._state[9] + + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) + self._state[9] = s9 + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.6/PyRandLib/mrg1457.py b/Python3.6/PyRandLib/mrg1457.py new file mode 100644 index 0000000..6ba04f1 --- /dev/null +++ b/Python3.6/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : int = 2_147_483_647 # i.e., 0xffff_ffff or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + k1 = self._index-1 + if k1 < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + k24 = self._index-24 + if k24 < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrg1457.py ======================================== diff --git a/Python3.6/PyRandLib/mrg287.py b/Python3.6/PyRandLib/mrg287.py new file mode 100644 index 0000000..de15971 --- /dev/null +++ b/Python3.6/PyRandLib/mrg287.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + k55 = self._index-55 + if k55 < 0: + k55 += Mrg287._STATE_SIZE + + k119 = self._index-119 + if k119 < 0: + k119 += Mrg287._STATE_SIZE + + k179 = self._index-179 + if k179 < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % self._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrg287.py ====================================== diff --git a/Python3.6/PyRandLib/mrg49507.py b/Python3.6/PyRandLib/mrg49507.py new file mode 100644 index 0000000..7635f25 --- /dev/null +++ b/Python3.6/PyRandLib/mrg49507.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + k7 = self._index-7 + if k7 < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrg49507.py ======================================= diff --git a/Python3.6/PyRandLib/pcg1024_32.py b/Python3.6/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..38968c8 --- /dev/null +++ b/Python3.6/PyRandLib/pcg1024_32.py @@ -0,0 +1,280 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _EXTENDED_STATE_SIZE: int = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return (self._extendedState[:], self._state) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + result ^= result >> 22 + + self._extendedState[i] = result + + return result == state & 0b11 + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.6/PyRandLib/pcg128_64.py b/Python3.6/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..b1750f4 --- /dev/null +++ b/Python3.6/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : int = (1 << 128) - 1 # notice: optimization on modulo calculations + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + current_state ^= current_state >> 64 # fixed shift XOR is then evaluated + value = current_state & 0xffff_ffff_ffff_ffff + rot_mask = (1 << random_rotation) - 1 + return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.6/PyRandLib/pcg64_32.py b/Python3.6/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..0227493 --- /dev/null +++ b/Python3.6/PyRandLib/pcg64_32.py @@ -0,0 +1,156 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + current_state ^= current_state >> 22 # fixed shift XOR is then evaluated + return (current_state >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = initRand() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.6/PyRandLib/splitmix.py b/Python3.6/PyRandLib/splitmix.py new file mode 100644 index 0000000..00be204 --- /dev/null +++ b/Python3.6/PyRandLib/splitmix.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state = (self._state + 0x9e37_79b9_7f4a_7c15) & 0xffff_ffff_ffff_ffff # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.6/PyRandLib/squares32.py b/Python3.6/PyRandLib/squares32.py new file mode 100644 index 0000000..9a88164 --- /dev/null +++ b/Python3.6/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.6/PyRandLib/squares64.py b/Python3.6/PyRandLib/squares64.py new file mode 100644 index 0000000..93d4c6e --- /dev/null +++ b/Python3.6/PyRandLib/squares64.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.6/PyRandLib/well1024a.py b/Python3.6/PyRandLib/well1024a.py new file mode 100644 index 0000000..3be6baf --- /dev/null +++ b/Python3.6/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.6/PyRandLib/well19937c.py b/Python3.6/PyRandLib/well19937c.py new file mode 100644 index 0000000..e5ae98d --- /dev/null +++ b/Python3.6/PyRandLib/well19937c.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 623 + else: + i_1, i_2 = 623, 622 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) + z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) + self._index = i_1 + + return self._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.6/PyRandLib/well44497b.py b/Python3.6/PyRandLib/well44497b.py new file mode 100644 index 0000000..5cfcd4b --- /dev/null +++ b/Python3.6/PyRandLib/well44497b.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 1390 + else: + i_1, i_2 = 1390, 1389 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 + self._index = i_1 + + return self._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.6/PyRandLib/well512a.py b/Python3.6/PyRandLib/well512a.py new file mode 100644 index 0000000..7bcc043 --- /dev/null +++ b/Python3.6/PyRandLib/well512a.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = BaseWELL._M3_neg(self._state[i], 16) ^ BaseWELL._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = BaseWELL._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = BaseWELL._M3_neg(z0, 2) ^ BaseWELL._M3_neg(z1, 18) ^ BaseWELL._M2_neg(z2, 28) ^ BaseWELL._M5_neg(z3, 5, BaseWELL._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.6/PyRandLib/xoroshiro1024.py b/Python3.6/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..ffb65f9 --- /dev/null +++ b/Python3.6/PyRandLib/xoroshiro1024.py @@ -0,0 +1,169 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + #------------------------------------------------------------------------- + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index += 1 + self._index &= self._SIZE_MODULO + sLow = self._state[ self._index ] + sHigh = self._state[ previousIndex ] ^ sLow + self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) + self._state[ self._index ] = self._rotleft( sHigh, 36 ) + # returns the output value + return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & self._SIZE_MODULO + except: + self._index = 0 + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.6/PyRandLib/xoroshiro256.py b/Python3.6/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..e706c97 --- /dev/null +++ b/Python3.6/PyRandLib/xoroshiro256.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.6/PyRandLib/xoroshiro512.py b/Python3.6/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..270e628 --- /dev/null +++ b/Python3.6/PyRandLib/xoroshiro512.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: int = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.6/testCPUPerfs.py b/Python3.6/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.6/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.6/testED.py b/Python3.6/testED.py new file mode 100644 index 0000000..2c4232a --- /dev/null +++ b/Python3.6/testED.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029 ) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029 ) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + +#===== end of module testED.py ========================================= diff --git a/Python3.9/PyRandLib/__init__.py b/Python3.9/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.9/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/Python3.9/PyRandLib/annotation_types.py b/Python3.9/PyRandLib/annotation_types.py new file mode 100644 index 0000000..8514250 --- /dev/null +++ b/Python3.9/PyRandLib/annotation_types.py @@ -0,0 +1,35 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +Numerical = Union[ int, float ] +StatesList = Union[ tuple[int], list[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.9/PyRandLib/basecwg.py b/Python3.9/PyRandLib/basecwg.py new file mode 100644 index 0000000..d8b51bf --- /dev/null +++ b/Python3.9/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.9/PyRandLib/baselcg.py b/Python3.9/PyRandLib/baselcg.py new file mode 100644 index 0000000..455c230 --- /dev/null +++ b/Python3.9/PyRandLib/baselcg.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.9/PyRandLib/baselfib64.py b/Python3.9/PyRandLib/baselfib64.py new file mode 100644 index 0000000..774b29a --- /dev/null +++ b/Python3.9/PyRandLib/baselfib64.py @@ -0,0 +1,207 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.9/PyRandLib/basemelg.py b/Python3.9/PyRandLib/basemelg.py new file mode 100644 index 0000000..09647ec --- /dev/null +++ b/Python3.9/PyRandLib/basemelg.py @@ -0,0 +1,201 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.9/PyRandLib/basemrg.py b/Python3.9/PyRandLib/basemrg.py new file mode 100644 index 0000000..3e9f728 --- /dev/null +++ b/Python3.9/PyRandLib/basemrg.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.9/PyRandLib/basepcg.py b/Python3.9/PyRandLib/basepcg.py new file mode 100644 index 0000000..b07a760 --- /dev/null +++ b/Python3.9/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.9/PyRandLib/basesquares.py b/Python3.9/PyRandLib/basesquares.py new file mode 100644 index 0000000..47017c6 --- /dev/null +++ b/Python3.9/PyRandLib/basesquares.py @@ -0,0 +1,161 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.9/PyRandLib/basewell.py b/Python3.9/PyRandLib/basewell.py new file mode 100644 index 0000000..968a29c --- /dev/null +++ b/Python3.9/PyRandLib/basewell.py @@ -0,0 +1,290 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.9/PyRandLib/basexoroshiro.py b/Python3.9/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..2fab10a --- /dev/null +++ b/Python3.9/PyRandLib/basexoroshiro.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.9/PyRandLib/cwg128.py b/Python3.9/PyRandLib/cwg128.py new file mode 100644 index 0000000..f5350bc --- /dev/null +++ b/Python3.9/PyRandLib/cwg128.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.9/PyRandLib/cwg128_64.py b/Python3.9/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..1ca37b4 --- /dev/null +++ b/Python3.9/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.9/PyRandLib/cwg64.py b/Python3.9/PyRandLib/cwg64.py new file mode 100644 index 0000000..1d52085 --- /dev/null +++ b/Python3.9/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.9/PyRandLib/fastrand32.py b/Python3.9/PyRandLib/fastrand32.py new file mode 100644 index 0000000..26a25c1 --- /dev/null +++ b/Python3.9/PyRandLib/fastrand32.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.9/PyRandLib/fastrand63.py b/Python3.9/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4e10144 --- /dev/null +++ b/Python3.9/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.9/PyRandLib/lfib116.py b/Python3.9/PyRandLib/lfib116.py new file mode 100644 index 0000000..5083c07 --- /dev/null +++ b/Python3.9/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.9/PyRandLib/lfib1340.py b/Python3.9/PyRandLib/lfib1340.py new file mode 100644 index 0000000..0f4ecdf --- /dev/null +++ b/Python3.9/PyRandLib/lfib1340.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.9/PyRandLib/lfib668.py b/Python3.9/PyRandLib/lfib668.py new file mode 100644 index 0000000..3ae6989 --- /dev/null +++ b/Python3.9/PyRandLib/lfib668.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.9/PyRandLib/lfib78.py b/Python3.9/PyRandLib/lfib78.py new file mode 100644 index 0000000..f2cdeda --- /dev/null +++ b/Python3.9/PyRandLib/lfib78.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.9/PyRandLib/melg19937.py b/Python3.9/PyRandLib/melg19937.py new file mode 100644 index 0000000..10a3332 --- /dev/null +++ b/Python3.9/PyRandLib/melg19937.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.9/PyRandLib/melg44497.py b/Python3.9/PyRandLib/melg44497.py new file mode 100644 index 0000000..5f14081 --- /dev/null +++ b/Python3.9/PyRandLib/melg44497.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.9/PyRandLib/melg607.py b/Python3.9/PyRandLib/melg607.py new file mode 100644 index 0000000..d6c10b6 --- /dev/null +++ b/Python3.9/PyRandLib/melg607.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.9/PyRandLib/mrg1457.py b/Python3.9/PyRandLib/mrg1457.py new file mode 100644 index 0000000..007b8fd --- /dev/null +++ b/Python3.9/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.9/PyRandLib/mrg287.py b/Python3.9/PyRandLib/mrg287.py new file mode 100644 index 0000000..093406d --- /dev/null +++ b/Python3.9/PyRandLib/mrg287.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.9/PyRandLib/mrg49507.py b/Python3.9/PyRandLib/mrg49507.py new file mode 100644 index 0000000..a196c65 --- /dev/null +++ b/Python3.9/PyRandLib/mrg49507.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.9/PyRandLib/pcg1024_32.py b/Python3.9/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..a6df012 --- /dev/null +++ b/Python3.9/PyRandLib/pcg1024_32.py @@ -0,0 +1,282 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.9/PyRandLib/pcg128_64.py b/Python3.9/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..ddef70e --- /dev/null +++ b/Python3.9/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.9/PyRandLib/pcg64_32.py b/Python3.9/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..4eed457 --- /dev/null +++ b/Python3.9/PyRandLib/pcg64_32.py @@ -0,0 +1,154 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.9/PyRandLib/splitmix.py b/Python3.9/PyRandLib/splitmix.py new file mode 100644 index 0000000..0b97a51 --- /dev/null +++ b/Python3.9/PyRandLib/splitmix.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.9/PyRandLib/squares32.py b/Python3.9/PyRandLib/squares32.py new file mode 100644 index 0000000..b58d642 --- /dev/null +++ b/Python3.9/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.9/PyRandLib/squares64.py b/Python3.9/PyRandLib/squares64.py new file mode 100644 index 0000000..3d38a45 --- /dev/null +++ b/Python3.9/PyRandLib/squares64.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.9/PyRandLib/well1024a.py b/Python3.9/PyRandLib/well1024a.py new file mode 100644 index 0000000..dfd7085 --- /dev/null +++ b/Python3.9/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.9/PyRandLib/well19937c.py b/Python3.9/PyRandLib/well19937c.py new file mode 100644 index 0000000..a89344f --- /dev/null +++ b/Python3.9/PyRandLib/well19937c.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + if (i := self._index) >= 2: + i_1, i_2 = i-1, i-2 + elif i == 1: + i_1, i_2 = 0, 623 + else: + i_1, i_2 = 623, 622 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.9/PyRandLib/well44497b.py b/Python3.9/PyRandLib/well44497b.py new file mode 100644 index 0000000..68ac9db --- /dev/null +++ b/Python3.9/PyRandLib/well44497b.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + if (i := self._index) >= 2: + i_1, i_2 = i-1, i-2 + elif i == 1: + i_1, i_2 = 0, 1390 + else: + i_1, i_2 = 1390, 1389 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.9/PyRandLib/well512a.py b/Python3.9/PyRandLib/well512a.py new file mode 100644 index 0000000..aaf532e --- /dev/null +++ b/Python3.9/PyRandLib/well512a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.9/PyRandLib/xoroshiro1024.py b/Python3.9/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..0503732 --- /dev/null +++ b/Python3.9/PyRandLib/xoroshiro1024.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + if (count := len( _seedState )) == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.9/PyRandLib/xoroshiro256.py b/Python3.9/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..84f5f3a --- /dev/null +++ b/Python3.9/PyRandLib/xoroshiro256.py @@ -0,0 +1,181 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + if (count := len( _seedState )) == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.9/PyRandLib/xoroshiro512.py b/Python3.9/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..0e19e70 --- /dev/null +++ b/Python3.9/PyRandLib/xoroshiro512.py @@ -0,0 +1,181 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + if (count := len( _seedState )) == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.9/testCPUPerfs.py b/Python3.9/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.9/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.9/testED.py b/Python3.9/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.9/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= diff --git a/testCPUPerfs.py b/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/testED.py b/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py =========================================