diff --git a/PyRandLib/basepcg.py b/PyRandLib/basepcg.py index a043307..8c9a423 100644 --- a/PyRandLib/basepcg.py +++ b/PyRandLib/basepcg.py @@ -27,7 +27,7 @@ #============================================================================= class BasePCG( BaseRandom ): - """Definition of the base class for all PCG pseudo-random generators. + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. This module is part of library PyRandLib. diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py index 2ef12d1..2968237 100644 --- a/PyRandLib/basesquares.py +++ b/PyRandLib/basesquares.py @@ -25,6 +25,7 @@ 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. diff --git a/PyRandLib/basewell.py b/PyRandLib/basewell.py index 0e623c3..b7dc2ac 100644 --- a/PyRandLib/basewell.py +++ b/PyRandLib/basewell.py @@ -22,7 +22,7 @@ #============================================================================= from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType +from .annotation_types import SeedStateType, StateType from .splitmix import SplitMix32 @@ -232,7 +232,7 @@ def _M3_neg(cls, x: int, t: int) -> int: @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 + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 #------------------------------------------------------------------------- @classmethod @@ -270,12 +270,10 @@ def _tempering(cls, x: int, b: int, c: int) -> int: #assert 0 <= b <= 0xffff_ffff #assert 0 <= c <= 0xffff_ffff #assert 0 <= w <= 32 - # z = ((z << (32 - w)) & 0xffff_ffff) >> (32 - w) - # 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 - z = x - z = z ^ (((z << 7) & 0xffff_ffff) & b) - return z ^ (((z << 15) & 0xffff_ffff) & c) + # 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 diff --git a/PyRandLib/basexoroshiro.py b/PyRandLib/basexoroshiro.py index 15351d7..1226391 100644 --- a/PyRandLib/basexoroshiro.py +++ b/PyRandLib/basexoroshiro.py @@ -107,7 +107,7 @@ class BaseXoroshiro( BaseRandom ): """ - _MODULO = (1 << 64) - 1 + _MODULO: int = (1 << 64) - 1 #------------------------------------------------------------------------- diff --git a/PyRandLib/lfib116.py b/PyRandLib/lfib116.py index 564e281..8f1f000 100644 --- a/PyRandLib/lfib116.py +++ b/PyRandLib/lfib116.py @@ -109,7 +109,7 @@ class LFib116( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers #------------------------------------------------------------------------- diff --git a/PyRandLib/lfib1340.py b/PyRandLib/lfib1340.py index 55e3fd7..5007045 100644 --- a/PyRandLib/lfib1340.py +++ b/PyRandLib/lfib1340.py @@ -110,7 +110,7 @@ class LFib1340( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers #------------------------------------------------------------------------- diff --git a/PyRandLib/lfib668.py b/PyRandLib/lfib668.py index 1ab373c..0f5e494 100644 --- a/PyRandLib/lfib668.py +++ b/PyRandLib/lfib668.py @@ -109,7 +109,7 @@ class LFib668( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers #------------------------------------------------------------------------- diff --git a/PyRandLib/lfib78.py b/PyRandLib/lfib78.py index 10e7fee..7f3589c 100644 --- a/PyRandLib/lfib78.py +++ b/PyRandLib/lfib78.py @@ -108,7 +108,7 @@ class LFib78( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers #------------------------------------------------------------------------- diff --git a/PyRandLib/melg19937.py b/PyRandLib/melg19937.py index cb34b3e..5d9b093 100644 --- a/PyRandLib/melg19937.py +++ b/PyRandLib/melg19937.py @@ -21,7 +21,7 @@ """ #============================================================================= -from .basemelg import BaseMELG +from .basemelg import BaseMELG #============================================================================= @@ -94,7 +94,7 @@ class Melg19937( BaseMELG ): #------------------------------------------------------------------------- # 'protected' constants - _STATE_SIZE = 312 + _STATE_SIZE: int = 312 _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' diff --git a/PyRandLib/melg44497.py b/PyRandLib/melg44497.py index 2b3875a..286cdd7 100644 --- a/PyRandLib/melg44497.py +++ b/PyRandLib/melg44497.py @@ -21,7 +21,7 @@ """ #============================================================================= -from .basemelg import BaseMELG +from .basemelg import BaseMELG #============================================================================= @@ -93,7 +93,7 @@ class Melg44497( BaseMELG ): #------------------------------------------------------------------------- # 'protected' constants - _STATE_SIZE = 696 + _STATE_SIZE: int = 696 _A_COND = (0, 0x4fa9_ca36_f293_c9a9) diff --git a/PyRandLib/melg607.py b/PyRandLib/melg607.py index 01691b8..6a38a55 100644 --- a/PyRandLib/melg607.py +++ b/PyRandLib/melg607.py @@ -21,7 +21,7 @@ """ #============================================================================= -from .basemelg import BaseMELG +from .basemelg import BaseMELG #============================================================================= @@ -93,7 +93,7 @@ class Melg607( BaseMELG ): #------------------------------------------------------------------------- # 'protected' constants - _STATE_SIZE = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _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... diff --git a/PyRandLib/mrg1457.py b/PyRandLib/mrg1457.py index 4480324..17f8c32 100644 --- a/PyRandLib/mrg1457.py +++ b/PyRandLib/mrg1457.py @@ -100,8 +100,8 @@ class Mrg1457( BaseMRG ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_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 + _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 #------------------------------------------------------------------------- diff --git a/PyRandLib/mrg287.py b/PyRandLib/mrg287.py index d2eccf7..119c3f0 100644 --- a/PyRandLib/mrg287.py +++ b/PyRandLib/mrg287.py @@ -114,8 +114,8 @@ class Mrg287( BaseMRG ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO = 0xffff_ffff + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff #------------------------------------------------------------------------- diff --git a/PyRandLib/mrg49507.py b/PyRandLib/mrg49507.py index bfbe142..2eb2597 100644 --- a/PyRandLib/mrg49507.py +++ b/PyRandLib/mrg49507.py @@ -100,8 +100,8 @@ class Mrg49507( BaseMRG ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_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 + _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 #------------------------------------------------------------------------- diff --git a/PyRandLib/pcg1024_32.py b/PyRandLib/pcg1024_32.py index 9812aff..c721499 100644 --- a/PyRandLib/pcg1024_32.py +++ b/PyRandLib/pcg1024_32.py @@ -111,7 +111,7 @@ class Pcg1024_32( Pcg64_32 ): """ #------------------------------------------------------------------------- - _EXTENDED_STATE_SIZE = 1024 + _EXTENDED_STATE_SIZE: int = 1024 #------------------------------------------------------------------------- diff --git a/PyRandLib/well1024a.py b/PyRandLib/well1024a.py index 01b8ef4..9f92c62 100644 --- a/PyRandLib/well1024a.py +++ b/PyRandLib/well1024a.py @@ -106,7 +106,7 @@ class Well1024a( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) #------------------------------------------------------------------------- diff --git a/PyRandLib/well19937c.py b/PyRandLib/well19937c.py index 0c18771..7b836e0 100644 --- a/PyRandLib/well19937c.py +++ b/PyRandLib/well19937c.py @@ -106,7 +106,7 @@ class Well19937c( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) #------------------------------------------------------------------------- diff --git a/PyRandLib/well44497b.py b/PyRandLib/well44497b.py index f022592..0157139 100644 --- a/PyRandLib/well44497b.py +++ b/PyRandLib/well44497b.py @@ -106,7 +106,7 @@ class Well44497b( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) #------------------------------------------------------------------------- diff --git a/PyRandLib/well512a.py b/PyRandLib/well512a.py index 1dd6043..42b14a0 100644 --- a/PyRandLib/well512a.py +++ b/PyRandLib/well512a.py @@ -107,7 +107,7 @@ class Well512a( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) #------------------------------------------------------------------------- diff --git a/PyRandLib/xoroshiro1024.py b/PyRandLib/xoroshiro1024.py index e0d8650..62d5cb6 100644 --- a/PyRandLib/xoroshiro1024.py +++ b/PyRandLib/xoroshiro1024.py @@ -78,8 +78,9 @@ class Xoroshiro1024( BaseXoroshiro ): should definitively pass. """ #------------------------------------------------------------------------- - _STATE_SIZE = 16 - _SIZE_MODULO = 0xf + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + #------------------------------------------------------------------------- def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: diff --git a/Python3.6/PyRandLib/__init__.py b/Python3.6/PyRandLib/__init__.py index bd75909..d5ac68a 100644 --- a/Python3.6/PyRandLib/__init__.py +++ b/Python3.6/PyRandLib/__init__.py @@ -42,3 +42,6 @@ from .xoroshiro256 import Xoroshiro256 from .xoroshiro512 import Xoroshiro512 from .xoroshiro1024 import Xoroshiro1024 + + +#===== end of package module __init__.py =============================== diff --git a/Python3.6/PyRandLib/basepcg.py b/Python3.6/PyRandLib/basepcg.py index a043307..3cd35f8 100644 --- a/Python3.6/PyRandLib/basepcg.py +++ b/Python3.6/PyRandLib/basepcg.py @@ -27,7 +27,7 @@ #============================================================================= class BasePCG( BaseRandom ): - """Definition of the base class for all PCG pseudo-random generators. + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. This module is part of library PyRandLib. @@ -114,4 +114,5 @@ def getstate(self) -> int: """ return self._state + #===== end of module basepcg.py ======================================== diff --git a/Python3.6/PyRandLib/basesquares.py b/Python3.6/PyRandLib/basesquares.py index 2ef12d1..2968237 100644 --- a/Python3.6/PyRandLib/basesquares.py +++ b/Python3.6/PyRandLib/basesquares.py @@ -25,6 +25,7 @@ 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. diff --git a/Python3.6/PyRandLib/basewell.py b/Python3.6/PyRandLib/basewell.py index 0e623c3..c040bd5 100644 --- a/Python3.6/PyRandLib/basewell.py +++ b/Python3.6/PyRandLib/basewell.py @@ -22,7 +22,7 @@ #============================================================================= from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType +from .annotation_types import SeedStateType, StateType from .splitmix import SplitMix32 @@ -232,7 +232,7 @@ def _M3_neg(cls, x: int, t: int) -> int: @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 + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 #------------------------------------------------------------------------- @classmethod @@ -256,7 +256,7 @@ def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: #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 + return (y ^ a) if (x & (1< int: def _tempering(cls, x: int, b: int, c: int) -> int: #assert 0 <= b <= 0xffff_ffff #assert 0 <= c <= 0xffff_ffff - #assert 0 <= w <= 32 - # z = ((z << (32 - w)) & 0xffff_ffff) >> (32 - w) - # 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 - z = x - z = z ^ (((z << 7) & 0xffff_ffff) & b) - return z ^ (((z << 15) & 0xffff_ffff) & c) + # 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 + # 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 index 15351d7..447c256 100644 --- a/Python3.6/PyRandLib/basexoroshiro.py +++ b/Python3.6/PyRandLib/basexoroshiro.py @@ -91,7 +91,6 @@ class BaseXoroshiro( BaseRandom ): 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 @@ -106,8 +105,7 @@ class BaseXoroshiro( BaseRandom ): than 32 bits. """ - - _MODULO = (1 << 64) - 1 + _MODULO: int = (1 << 64) - 1 #------------------------------------------------------------------------- diff --git a/Python3.6/PyRandLib/cwg128.py b/Python3.6/PyRandLib/cwg128.py index 88db42c..637d2e6 100644 --- a/Python3.6/PyRandLib/cwg128.py +++ b/Python3.6/PyRandLib/cwg128.py @@ -86,7 +86,6 @@ class Cwg128( BaseCWG ): * _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) @@ -102,8 +101,7 @@ class Cwg128( BaseCWG ): than 32 bits. """ - - _MODULO: int = (1 << 128) - 1 + _MODULO: int = (1 << 128) - 1 # Notice: optimization on modulo computation #------------------------------------------------------------------------- @@ -130,7 +128,7 @@ def next(self) -> int: #------------------------------------------------------------------------- - def getstate(self) -> Tuple[int]: + 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. @@ -165,4 +163,5 @@ def setstate(self, _state: SeedStateType = None) -> None: # 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 index 13ec38a..e529f7b 100644 --- a/Python3.6/PyRandLib/cwg128_64.py +++ b/Python3.6/PyRandLib/cwg128_64.py @@ -83,7 +83,6 @@ class Cwg128_64( BaseCWG ): 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 diff --git a/Python3.6/PyRandLib/cwg64.py b/Python3.6/PyRandLib/cwg64.py index f8f5081..ae186ee 100644 --- a/Python3.6/PyRandLib/cwg64.py +++ b/Python3.6/PyRandLib/cwg64.py @@ -83,7 +83,6 @@ class Cwg64( BaseCWG ): 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 diff --git a/Python3.6/PyRandLib/fastrand32.py b/Python3.6/PyRandLib/fastrand32.py index 9813c66..9fa1d0e 100644 --- a/Python3.6/PyRandLib/fastrand32.py +++ b/Python3.6/PyRandLib/fastrand32.py @@ -119,4 +119,5 @@ def setstate(self, _state: Numerical) -> None: initRand = SplitMix32() self._state = initRand() + #===== end of module fastrand32.py ===================================== diff --git a/Python3.6/PyRandLib/fastrand63.py b/Python3.6/PyRandLib/fastrand63.py index 4483c40..9d8f78b 100644 --- a/Python3.6/PyRandLib/fastrand63.py +++ b/Python3.6/PyRandLib/fastrand63.py @@ -90,7 +90,6 @@ class FastRand63( BaseLCG ): 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 diff --git a/Python3.6/PyRandLib/lfib116.py b/Python3.6/PyRandLib/lfib116.py index 564e281..c0b3e63 100644 --- a/Python3.6/PyRandLib/lfib116.py +++ b/Python3.6/PyRandLib/lfib116.py @@ -109,7 +109,7 @@ class LFib116( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers #------------------------------------------------------------------------- @@ -129,5 +129,6 @@ def next(self) -> int: 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 index 55e3fd7..cc0569e 100644 --- a/Python3.6/PyRandLib/lfib1340.py +++ b/Python3.6/PyRandLib/lfib1340.py @@ -110,7 +110,7 @@ class LFib1340( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers #------------------------------------------------------------------------- @@ -130,5 +130,6 @@ def next(self) -> int: 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 index 1ab373c..fa60d74 100644 --- a/Python3.6/PyRandLib/lfib668.py +++ b/Python3.6/PyRandLib/lfib668.py @@ -109,7 +109,7 @@ class LFib668( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers #------------------------------------------------------------------------- @@ -130,4 +130,5 @@ def next(self) -> int: return myValue + #===== end of module lfib668.py ======================================= diff --git a/Python3.6/PyRandLib/lfib78.py b/Python3.6/PyRandLib/lfib78.py index 10e7fee..a50d034 100644 --- a/Python3.6/PyRandLib/lfib78.py +++ b/Python3.6/PyRandLib/lfib78.py @@ -108,7 +108,7 @@ class LFib78( BaseLFib64 ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers #------------------------------------------------------------------------- @@ -128,5 +128,6 @@ def next(self) -> int: 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 index cb34b3e..49aa010 100644 --- a/Python3.6/PyRandLib/melg19937.py +++ b/Python3.6/PyRandLib/melg19937.py @@ -21,7 +21,7 @@ """ #============================================================================= -from .basemelg import BaseMELG +from .basemelg import BaseMELG #============================================================================= @@ -94,7 +94,7 @@ class Melg19937( BaseMELG ): #------------------------------------------------------------------------- # 'protected' constants - _STATE_SIZE = 312 + _STATE_SIZE: int = 312 _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' @@ -118,4 +118,4 @@ def next(self) -> int: return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) -#===== end of module melg607.py ======================================== +#===== end of module melg19937.py diff --git a/Python3.6/PyRandLib/melg44497.py b/Python3.6/PyRandLib/melg44497.py index 2b3875a..ef2a706 100644 --- a/Python3.6/PyRandLib/melg44497.py +++ b/Python3.6/PyRandLib/melg44497.py @@ -21,7 +21,7 @@ """ #============================================================================= -from .basemelg import BaseMELG +from .basemelg import BaseMELG #============================================================================= @@ -93,7 +93,7 @@ class Melg44497( BaseMELG ): #------------------------------------------------------------------------- # 'protected' constants - _STATE_SIZE = 696 + _STATE_SIZE: int = 696 _A_COND = (0, 0x4fa9_ca36_f293_c9a9) @@ -117,4 +117,4 @@ def next(self) -> int: return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) -#===== end of module melg607.py ======================================== +#===== end of module melg44977.py ====================================== diff --git a/Python3.6/PyRandLib/melg607.py b/Python3.6/PyRandLib/melg607.py index 01691b8..c58f1f7 100644 --- a/Python3.6/PyRandLib/melg607.py +++ b/Python3.6/PyRandLib/melg607.py @@ -21,7 +21,7 @@ """ #============================================================================= -from .basemelg import BaseMELG +from .basemelg import BaseMELG #============================================================================= @@ -93,8 +93,8 @@ class Melg607( BaseMELG ): #------------------------------------------------------------------------- # 'protected' constants - _STATE_SIZE = 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... + _STATE_SIZE: int = 10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # Notice: this tuple will avoid an 'if' in method 'next()' #------------------------------------------------------------------------- @@ -110,11 +110,11 @@ def next(self) -> int: 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 + 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)) # 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... - + 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 index 4480324..6ba04f1 100644 --- a/Python3.6/PyRandLib/mrg1457.py +++ b/Python3.6/PyRandLib/mrg1457.py @@ -99,9 +99,9 @@ class Mrg1457( BaseMRG ): #------------------------------------------------------------------------- - # 'protected' constant - _STATE_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 + # '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 #------------------------------------------------------------------------- @@ -145,4 +145,5 @@ def next(self) -> int: # then returns the integer generated value return myValue -#===== end of module mrgrand1457.py ==================================== + +#===== end of module mrg1457.py ======================================== diff --git a/Python3.6/PyRandLib/mrg287.py b/Python3.6/PyRandLib/mrg287.py index d2eccf7..de15971 100644 --- a/Python3.6/PyRandLib/mrg287.py +++ b/Python3.6/PyRandLib/mrg287.py @@ -113,9 +113,9 @@ class Mrg287( BaseMRG ): """ #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO = 0xffff_ffff + # 'protected' constants + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff #------------------------------------------------------------------------- @@ -148,4 +148,5 @@ def next(self) -> int: # then returns the integer generated value return myValue -#===== end of module mrgrand287.py ================================== + +#===== end of module mrg287.py ====================================== diff --git a/Python3.6/PyRandLib/mrg49507.py b/Python3.6/PyRandLib/mrg49507.py index bfbe142..7635f25 100644 --- a/Python3.6/PyRandLib/mrg49507.py +++ b/Python3.6/PyRandLib/mrg49507.py @@ -99,9 +99,9 @@ class Mrg49507( BaseMRG ): """ #------------------------------------------------------------------------- - # 'protected' constant - _STATE_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 + # '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 #------------------------------------------------------------------------- @@ -138,4 +138,4 @@ def next(self) -> int: # then returns the integer generated value return myValue -#===== end of module mrgrand49507.py =================================== +#===== end of module mrg49507.py ======================================= diff --git a/Python3.6/PyRandLib/pcg1024_32.py b/Python3.6/PyRandLib/pcg1024_32.py index 9812aff..38968c8 100644 --- a/Python3.6/PyRandLib/pcg1024_32.py +++ b/Python3.6/PyRandLib/pcg1024_32.py @@ -111,7 +111,7 @@ class Pcg1024_32( Pcg64_32 ): """ #------------------------------------------------------------------------- - _EXTENDED_STATE_SIZE = 1024 + _EXTENDED_STATE_SIZE: int = 1024 #------------------------------------------------------------------------- @@ -144,7 +144,7 @@ def getstate(self) -> StateType: 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 ] + return (self._extendedState[:], self._state) #------------------------------------------------------------------------- @@ -176,7 +176,7 @@ def setstate(self, _seedState: SeedStateType) -> None: # 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: + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: self._extendedState = _seedState[0][:] self._state = _seedState[1] elif extendedCount > 0: @@ -237,7 +237,7 @@ def _initextendedstate(self, _initialSeed: Numerical = None) -> None: """ # feeds the list according to an initial seed. initRand = SplitMix32( _initialSeed ) - self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] #------------------------------------------------------------------------- diff --git a/Python3.6/PyRandLib/pcg128_64.py b/Python3.6/PyRandLib/pcg128_64.py index 6f9eb84..b1750f4 100644 --- a/Python3.6/PyRandLib/pcg128_64.py +++ b/Python3.6/PyRandLib/pcg128_64.py @@ -102,7 +102,6 @@ class Pcg128_64( BasePCG ): 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 @@ -117,8 +116,7 @@ class Pcg128_64( BasePCG ): than 32 bits. """ - - _MODULO_128 : int = (1 << 128) - 1 + _MODULO_128 : int = (1 << 128) - 1 # notice: optimization on modulo calculations #------------------------------------------------------------------------- @@ -128,7 +126,7 @@ def __init__(self, _seed: Numerical = None) -> None: 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 + super().__init__( _seed ) # this call creates attribute self._state and sets it #------------------------------------------------------------------------- @@ -137,7 +135,7 @@ def next(self) -> int: """ # 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 + 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 @@ -157,20 +155,21 @@ def setstate(self, _state: Numerical) -> None: """ if isinstance( _state, int ): # passed initial seed is an integer, just uses it - self._state = _state & self._MODULO_128 + 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 ) & self._MODULO_128 + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 else: - self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 + 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 index 1fa867e..0227493 100644 --- a/Python3.6/PyRandLib/pcg64_32.py +++ b/Python3.6/PyRandLib/pcg64_32.py @@ -152,4 +152,5 @@ def setstate(self, _state: Numerical) -> None: 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 index 2d674e8..00be204 100644 --- a/Python3.6/PyRandLib/splitmix.py +++ b/Python3.6/PyRandLib/splitmix.py @@ -46,7 +46,7 @@ class SplitMix64: (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). """ #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: + def __init__(self, _seed: Numerical = None, /) -> None: """Constructor. Should _seed be None, the internal system local time is used as the initial seed @@ -70,14 +70,13 @@ def __init__(self, _seed: Numerical = None) -> None: #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: + 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 + 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 @@ -97,7 +96,7 @@ class SplitMix63( SplitMix64 ): This class evaluates "random" values on 63 bits. """ #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: + def __init__(self, _seed: Numerical = None, /) -> None: """Constructor. Should _seed be None, the internal system local time is used as the initial seed @@ -105,7 +104,7 @@ def __init__(self, _seed: Numerical = None) -> None: super().__init__( _seed ) #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: + def __call__(self, _seed: int = None, /) -> int: """The split-mix algorithm. """ # returns the 63 higher bits generated by base class operator () @@ -123,7 +122,7 @@ class SplitMix32( SplitMix64 ): This class evaluates "random" values on 32 bits. """ #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: + def __init__(self, _seed: Numerical = None, /) -> None: """Constructor. Should _seed be None, the internal system local time is used as the initial seed @@ -131,8 +130,11 @@ def __init__(self, _seed: Numerical = None) -> None: super().__init__( _seed ) #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: + 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 index 4e79735..d504365 100644 --- a/Python3.6/PyRandLib/squares32.py +++ b/Python3.6/PyRandLib/squares32.py @@ -91,8 +91,7 @@ def next(self) -> int: Return a 32-bits value. """ - self._counter += 1 - self._counter &= 0xffff_ffff_ffff_ffff + 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 @@ -107,4 +106,5 @@ def next(self) -> int: # 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 index e58bf39..93d4c6e 100644 --- a/Python3.6/PyRandLib/squares64.py +++ b/Python3.6/PyRandLib/squares64.py @@ -21,6 +21,8 @@ """ #============================================================================= +from typing import Final + from .basesquares import BaseSquares from .annotation_types import SeedStateType, StatesList @@ -76,14 +78,14 @@ class Squares64( BaseSquares ): #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + _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: int = 64 + _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. @@ -110,8 +112,8 @@ def next(self) -> int: Returns a 64-bits value. """ - self._counter +=1 - self._counter &= 0xffff_ffff_ffff_ffff + 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 @@ -129,4 +131,5 @@ def next(self) -> int: # 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 index 01b8ef4..3be6baf 100644 --- a/Python3.6/PyRandLib/well1024a.py +++ b/Python3.6/PyRandLib/well1024a.py @@ -23,6 +23,7 @@ #============================================================================= from .basewell import BaseWELL + #============================================================================= class Well1024a( BaseWELL ): """ @@ -106,7 +107,7 @@ class Well1024a( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) #------------------------------------------------------------------------- @@ -133,4 +134,5 @@ def next(self) -> int: 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 index 0c18771..e5ae98d 100644 --- a/Python3.6/PyRandLib/well19937c.py +++ b/Python3.6/PyRandLib/well19937c.py @@ -23,6 +23,7 @@ #============================================================================= from .basewell import BaseWELL + #============================================================================= class Well19937c( BaseWELL ): """ @@ -106,7 +107,7 @@ class Well19937c( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) #------------------------------------------------------------------------- @@ -132,4 +133,5 @@ def next(self) -> int: 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 index f022592..5cfcd4b 100644 --- a/Python3.6/PyRandLib/well44497b.py +++ b/Python3.6/PyRandLib/well44497b.py @@ -23,6 +23,7 @@ #============================================================================= from .basewell import BaseWELL + #============================================================================= class Well44497b( BaseWELL ): """ @@ -106,7 +107,7 @@ class Well44497b( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) #------------------------------------------------------------------------- @@ -132,4 +133,5 @@ def next(self) -> int: 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 index 1dd6043..7bcc043 100644 --- a/Python3.6/PyRandLib/well512a.py +++ b/Python3.6/PyRandLib/well512a.py @@ -23,6 +23,7 @@ #============================================================================= from .basewell import BaseWELL + #============================================================================= class Well512a( BaseWELL ): """ @@ -107,29 +108,29 @@ class Well512a( BaseWELL ): #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + _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 + 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 = 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) + 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] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + 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 index e0d8650..ffb65f9 100644 --- a/Python3.6/PyRandLib/xoroshiro1024.py +++ b/Python3.6/PyRandLib/xoroshiro1024.py @@ -21,7 +21,7 @@ """ #============================================================================= -from typing import Tuple, Union +from typing import Union from .basexoroshiro import BaseXoroshiro from .annotation_types import Numerical, SeedStateType, StatesListAndExt @@ -78,8 +78,9 @@ class Xoroshiro1024( BaseXoroshiro ): should definitively pass. """ #------------------------------------------------------------------------- - _STATE_SIZE = 16 - _SIZE_MODULO = 0xf + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + #------------------------------------------------------------------------- def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: @@ -105,18 +106,6 @@ def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: 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 @@ -129,16 +118,6 @@ def next(self) -> int: 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. @@ -186,18 +165,5 @@ def _initindex(self, _index: int) -> None: 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/Python3.6/PyRandLib/xoroshiro256.py b/Python3.6/PyRandLib/xoroshiro256.py index 0b09b63..e706c97 100644 --- a/Python3.6/PyRandLib/xoroshiro256.py +++ b/Python3.6/PyRandLib/xoroshiro256.py @@ -23,6 +23,7 @@ #============================================================================= from typing import Tuple, Union +from .baserandom import BaseRandom from .basexoroshiro import BaseXoroshiro from .annotation_types import Numerical, StatesList from .splitmix import SplitMix64 @@ -113,10 +114,10 @@ def next(self) -> int: 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 ) + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO #------------------------------------------------------------------------- diff --git a/Python3.6/PyRandLib/xoroshiro512.py b/Python3.6/PyRandLib/xoroshiro512.py index 73ed1c8..270e628 100644 --- a/Python3.6/PyRandLib/xoroshiro512.py +++ b/Python3.6/PyRandLib/xoroshiro512.py @@ -21,8 +21,9 @@ """ #============================================================================= -from typing import Tuple, Union +from typing import Union +from .baserandom import BaseRandom from .basexoroshiro import BaseXoroshiro from .annotation_types import Numerical, StatesList from .splitmix import SplitMix64 @@ -77,6 +78,10 @@ class Xoroshiro512( BaseXoroshiro ): * _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: @@ -112,20 +117,10 @@ def next(self) -> int: 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 ) + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._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) + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO #------------------------------------------------------------------------- @@ -144,7 +139,7 @@ def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: """ try: count = len( _seedState ) - + if count == 0: self._initstate() @@ -152,7 +147,7 @@ def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: self._initstate( _seedState[0] ) else: - if (len(_seedState[0]) == self._STATE_SIZE): + 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] ) @@ -161,18 +156,5 @@ def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: 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.6/testED.py b/Python3.6/testED.py index 91a4a67..2c4232a 100644 --- a/Python3.6/testED.py +++ b/Python3.6/testED.py @@ -109,7 +109,7 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): #============================================================================= if __name__ == "__main__": - test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + 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) @@ -131,12 +131,11 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_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(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..cadc09d --- /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 Union + +Numerical = Union[ int, float ] +StatesList = Union[ tuple[int], list[int] ] +StatesListAndExt = Union[ tuple[ StatesList, int ], tuple[ StatesList, int, 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..9378da2 --- /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. + """ + raise NotImplementedError() + + +#===== 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..28addc3 --- /dev/null +++ b/Python3.9/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/Python3.9/PyRandLib/baserandom.py b/Python3.9/PyRandLib/baserandom.py new file mode 100644 index 0000000..255de2a --- /dev/null +++ b/Python3.9/PyRandLib/baserandom.py @@ -0,0 +1,378 @@ +""" +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 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. + | + | + | 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. + | + | + | 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. + """ + if k < 0: + raise (ValueError, "the returned bits count must not be negative") + if k > self._OUT_BITS: + raise (ValueError, f"the returned bits count must be at most {self._OUT_BITS}") + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + 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 + 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.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..d0f7e08 --- /dev/null +++ b/Python3.9/PyRandLib/cwg128.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 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 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. + """ + return (self._a, self._weyl, self._s, self._state) + + + #------------------------------------------------------------------------- + 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..27421a5 --- /dev/null +++ b/Python3.9/PyRandLib/cwg128_64.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 + +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 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. + """ + return (self._a, self._weyl, self._s, self._state) + + + #------------------------------------------------------------------------- + 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..20b5029 --- /dev/null +++ b/Python3.9/PyRandLib/cwg64.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 + +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 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. + """ + return (self._a, self._weyl, self._s, self._state) + + + #------------------------------------------------------------------------- + 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..a7e7eb2 --- /dev/null +++ b/Python3.9/PyRandLib/mrg1457.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 + +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..1337c8a --- /dev/null +++ b/Python3.9/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 + +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..27583af --- /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 += 1 + self._counter &= 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..ebb5621 --- /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 +=1 + self._counter &= 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/README.md b/README.md index ab90036..3505019 100644 --- a/README.md +++ b/README.md @@ -115,23 +115,23 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme | Cwg64 | 0.60 | | | | | 0 | 0 | 0 | | Cwg128_64 | 0.60 | | | | | 0 | 0 | 0 | | Cwg128 | 0.63 | | | | | 0 | 0 | 0 | - | FastRand32 | 0.20 | | | | | 11 | 106 | *too many* | + | FastRand32 | 0.19 | | | | | 11 | 106 | *too many* | | FastRand63 | 0.21 | | | | | 0 | 5 | 7 | | LFib78 | 0.35 | | | | | 0 | 0 | 0 | | LFib116 | 0.35 | | | | | 0 | 0 | 0 | | LFib668 | 0.37 | | | | | 0 | 0 | 0 | | LFib1340 | 0.39 | | | | | 0 | 0 | 0 | | Melg607 | 1.00 | | | | | 0 | 0 | 0 | - | Melg19937 | 1.07 | | | | | 0 | 0 | 0 | - | Melg44497 | 1.03 | | | | | 0 | 0 | 0 | + | Melg19937 | 1.04 | | | | | 0 | 0 | 0 | + | Melg44497 | 1.02 | | | | | 0 | 0 | 0 | | Mrg287 | 0.57 | | | | | 0 | 0 | 0 | | Mrg1457 | 0.58 | | | | | 0 | 0 | 0 | | Mrg49507 | 0.54 | | | | | 0 | 0 | 0 | | Pcg64_32 | 0.39 | | | | | 0 | 0 | 0 | | Pcg128_64 | 0.57 | | | | | 0 | 0 | 0 | | Pcg1024_32 | 0.80 | | | | | 0 | 0 | 0 | - | Squares32 | 1.23 | | | | | 0 | 0 | 0 | - | Squares64 | 1.49 | | | | | 0 | 0 | 0 | + | Squares32 | 1.18 | | | | | 0 | 0 | 0 | + | Squares64 | 1.48 | | | | | 0 | 0 | 0 | | Well512a | 1.95 | | | | | n.a. | n.a. | n.a. | | Well1024a | 1.80 | | | | | 0 | 4 | 4 | | Well19937b (1) | 2.43 | | | | | 0 | 2 | 2 | @@ -190,7 +190,9 @@ 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: +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. + +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. @@ -211,6 +213,8 @@ In **PyRandLib**, the Squares32 and Squares64 versions of the algorithm are impl 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. 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. 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**. diff --git a/testED.py b/testED.py index 2a081d3..91a4a67 100644 --- a/testED.py +++ b/testED.py @@ -72,7 +72,7 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): hist[n] += 1 # uncomment next line if you want to print the content of the histograms - print(hist, '\n') + #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)