From 5a9c1ecf49995b95cfdb7c4fe3cae53b539bce41 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 18:43:56 +0100 Subject: [PATCH 1/3] #55-enhance README.md Fixed one paragraph. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c1b867..4ba4951 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,7 @@ Latest version of **PyRandLib** is version **2.0**, released by March 2025. It p ### Why not Mersenne twister? -The Mersenne twister PRNG proposed by Matsumoto and Nishimura - see [5] - is the most widely used one. The Random class of module random in Python -implements this PRNG. It is also implemented in C++ and Java standard -libraries for instance. +The Mersenne twister PRNG proposed by Matsumoto and Nishimura - see [5] - is the most widely used one. The Random class of module random in Python implements this PRNG. It is also implemented in C++ and Java standard libraries for instance. It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, this PRNG is a little bit long to compute (up to 3 times than LCGs, 60% more than LFibs and a little bit less than MRGs, see below at section 'Architecture overview'). Moreover, it fails four of the hardest TestU01 tests. You can still use it as your preferred PRNG but **PyRandLib** implements many other PRNGs that are either far faster or far better in terms of generated pseudo-randomness than the Mersenne twister PRNG. From dd696228dda8110c5b3a58c70a3e3d526b8cd161 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 19:13:19 +0100 Subject: [PATCH 2/3] #142-Release v2.0 Removed unused directories - to prepare release 2.0.0. --- PyRandLib-org/__init__.py | 44 -- PyRandLib-org/annotation_types.py | 35 -- PyRandLib-org/basecwg.py | 113 ----- PyRandLib-org/baselcg.py | 104 ----- PyRandLib-org/baselfib64.py | 205 --------- PyRandLib-org/basemelg.py | 199 --------- PyRandLib-org/basemrg.py | 183 -------- PyRandLib-org/basepcg.py | 117 ----- PyRandLib-org/baserandom.py | 401 ----------------- PyRandLib-org/basesquares.py | 165 ------- PyRandLib-org/basewell.py | 307 ------------- PyRandLib-org/basexoroshiro.py | 190 --------- PyRandLib-org/cwg128.py | 159 ------- PyRandLib-org/cwg128_64.py | 153 ------- PyRandLib-org/cwg64.py | 153 ------- PyRandLib-org/fastrand32.py | 122 ------ PyRandLib-org/fastrand63.py | 134 ------ PyRandLib-org/lfib116.py | 133 ------ PyRandLib-org/lfib1340.py | 134 ------ PyRandLib-org/lfib668.py | 133 ------ PyRandLib-org/lfib78.py | 132 ------ PyRandLib-org/melg19937.py | 121 ------ PyRandLib-org/melg44497.py | 120 ------ PyRandLib-org/melg607.py | 120 ------ PyRandLib-org/mrg1457.py | 148 ------- PyRandLib-org/mrg287.py | 151 ------- PyRandLib-org/mrg49507.py | 141 ------ PyRandLib-org/pcg1024_32.py | 280 ------------ PyRandLib-org/pcg128_64.py | 176 -------- PyRandLib-org/pcg64_32.py | 155 ------- PyRandLib-org/splitmix.py | 138 ------ PyRandLib-org/squares32.py | 110 ----- PyRandLib-org/squares64.py | 132 ------ PyRandLib-org/well1024a.py | 136 ------ PyRandLib-org/well19937c.py | 135 ------ PyRandLib-org/well44497b.py | 135 ------ PyRandLib-org/well512a.py | 135 ------ PyRandLib-org/xoroshiro1024.py | 204 --------- PyRandLib-org/xoroshiro256.py | 182 -------- PyRandLib-org/xoroshiro512.py | 178 -------- PyRandLib/LICENSE | 21 - PyRandLib/README.md | 686 ------------------------------ PyRandLib/__init__.py | 44 -- PyRandLib/annotation_types.py | 35 -- PyRandLib/basecwg.py | 113 ----- PyRandLib/baselcg.py | 104 ----- PyRandLib/baselfib64.py | 205 --------- PyRandLib/basemelg.py | 199 --------- PyRandLib/basemrg.py | 183 -------- PyRandLib/basepcg.py | 117 ----- PyRandLib/baserandom.py | 401 ----------------- PyRandLib/basesquares.py | 165 ------- PyRandLib/basewell.py | 307 ------------- PyRandLib/basexoroshiro.py | 190 --------- PyRandLib/cwg128.py | 159 ------- PyRandLib/cwg128_64.py | 153 ------- PyRandLib/cwg64.py | 153 ------- PyRandLib/fastrand32.py | 122 ------ PyRandLib/fastrand63.py | 134 ------ PyRandLib/lfib116.py | 133 ------ PyRandLib/lfib1340.py | 134 ------ PyRandLib/lfib668.py | 133 ------ PyRandLib/lfib78.py | 132 ------ PyRandLib/melg19937.py | 121 ------ PyRandLib/melg44497.py | 120 ------ PyRandLib/melg607.py | 120 ------ PyRandLib/mrg1457.py | 148 ------- PyRandLib/mrg287.py | 151 ------- PyRandLib/mrg49507.py | 141 ------ PyRandLib/mrgrand1457.py | 135 ------ PyRandLib/mrgrand287.py | 155 ------- PyRandLib/mrgrand49507.py | 130 ------ PyRandLib/pcg1024_32.py | 280 ------------ PyRandLib/pcg128_64.py | 176 -------- PyRandLib/pcg64_32.py | 155 ------- PyRandLib/splitmix.py | 138 ------ PyRandLib/squares32.py | 110 ----- PyRandLib/squares64.py | 132 ------ PyRandLib/types.py | 33 -- PyRandLib/well1024a.py | 136 ------ PyRandLib/well19937c.py | 135 ------ PyRandLib/well44497b.py | 135 ------ PyRandLib/well512a.py | 135 ------ PyRandLib/xoroshiro1024.py | 204 --------- PyRandLib/xoroshiro256.py | 182 -------- PyRandLib/xoroshiro512.py | 178 -------- testCPUPerfs.py | 81 ---- testED.py | 142 ------- 88 files changed, 13809 deletions(-) delete mode 100644 PyRandLib-org/__init__.py delete mode 100644 PyRandLib-org/annotation_types.py delete mode 100644 PyRandLib-org/basecwg.py delete mode 100644 PyRandLib-org/baselcg.py delete mode 100644 PyRandLib-org/baselfib64.py delete mode 100644 PyRandLib-org/basemelg.py delete mode 100644 PyRandLib-org/basemrg.py delete mode 100644 PyRandLib-org/basepcg.py delete mode 100644 PyRandLib-org/baserandom.py delete mode 100644 PyRandLib-org/basesquares.py delete mode 100644 PyRandLib-org/basewell.py delete mode 100644 PyRandLib-org/basexoroshiro.py delete mode 100644 PyRandLib-org/cwg128.py delete mode 100644 PyRandLib-org/cwg128_64.py delete mode 100644 PyRandLib-org/cwg64.py delete mode 100644 PyRandLib-org/fastrand32.py delete mode 100644 PyRandLib-org/fastrand63.py delete mode 100644 PyRandLib-org/lfib116.py delete mode 100644 PyRandLib-org/lfib1340.py delete mode 100644 PyRandLib-org/lfib668.py delete mode 100644 PyRandLib-org/lfib78.py delete mode 100644 PyRandLib-org/melg19937.py delete mode 100644 PyRandLib-org/melg44497.py delete mode 100644 PyRandLib-org/melg607.py delete mode 100644 PyRandLib-org/mrg1457.py delete mode 100644 PyRandLib-org/mrg287.py delete mode 100644 PyRandLib-org/mrg49507.py delete mode 100644 PyRandLib-org/pcg1024_32.py delete mode 100644 PyRandLib-org/pcg128_64.py delete mode 100644 PyRandLib-org/pcg64_32.py delete mode 100644 PyRandLib-org/splitmix.py delete mode 100644 PyRandLib-org/squares32.py delete mode 100644 PyRandLib-org/squares64.py delete mode 100644 PyRandLib-org/well1024a.py delete mode 100644 PyRandLib-org/well19937c.py delete mode 100644 PyRandLib-org/well44497b.py delete mode 100644 PyRandLib-org/well512a.py delete mode 100644 PyRandLib-org/xoroshiro1024.py delete mode 100644 PyRandLib-org/xoroshiro256.py delete mode 100644 PyRandLib-org/xoroshiro512.py delete mode 100644 PyRandLib/LICENSE delete mode 100644 PyRandLib/README.md delete mode 100644 PyRandLib/__init__.py delete mode 100644 PyRandLib/annotation_types.py delete mode 100644 PyRandLib/basecwg.py delete mode 100644 PyRandLib/baselcg.py delete mode 100644 PyRandLib/baselfib64.py delete mode 100644 PyRandLib/basemelg.py delete mode 100644 PyRandLib/basemrg.py delete mode 100644 PyRandLib/basepcg.py delete mode 100644 PyRandLib/baserandom.py delete mode 100644 PyRandLib/basesquares.py delete mode 100644 PyRandLib/basewell.py delete mode 100644 PyRandLib/basexoroshiro.py delete mode 100644 PyRandLib/cwg128.py delete mode 100644 PyRandLib/cwg128_64.py delete mode 100644 PyRandLib/cwg64.py delete mode 100644 PyRandLib/fastrand32.py delete mode 100644 PyRandLib/fastrand63.py delete mode 100644 PyRandLib/lfib116.py delete mode 100644 PyRandLib/lfib1340.py delete mode 100644 PyRandLib/lfib668.py delete mode 100644 PyRandLib/lfib78.py delete mode 100644 PyRandLib/melg19937.py delete mode 100644 PyRandLib/melg44497.py delete mode 100644 PyRandLib/melg607.py delete mode 100644 PyRandLib/mrg1457.py delete mode 100644 PyRandLib/mrg287.py delete mode 100644 PyRandLib/mrg49507.py delete mode 100644 PyRandLib/mrgrand1457.py delete mode 100644 PyRandLib/mrgrand287.py delete mode 100644 PyRandLib/mrgrand49507.py delete mode 100644 PyRandLib/pcg1024_32.py delete mode 100644 PyRandLib/pcg128_64.py delete mode 100644 PyRandLib/pcg64_32.py delete mode 100644 PyRandLib/splitmix.py delete mode 100644 PyRandLib/squares32.py delete mode 100644 PyRandLib/squares64.py delete mode 100644 PyRandLib/types.py delete mode 100644 PyRandLib/well1024a.py delete mode 100644 PyRandLib/well19937c.py delete mode 100644 PyRandLib/well44497b.py delete mode 100644 PyRandLib/well512a.py delete mode 100644 PyRandLib/xoroshiro1024.py delete mode 100644 PyRandLib/xoroshiro256.py delete mode 100644 PyRandLib/xoroshiro512.py delete mode 100644 testCPUPerfs.py delete mode 100644 testED.py diff --git a/PyRandLib-org/__init__.py b/PyRandLib-org/__init__.py deleted file mode 100644 index bd75909..0000000 --- a/PyRandLib-org/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -This file is part of library PyRandLib. -It is provided under MIT License. -Please see files README.md and LICENSE. - -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com -""" - -from .basecwg import BaseCWG -from .baselcg import BaseLCG -from .baselfib64 import BaseLFib64 -from .basemelg import BaseMELG -from .basemrg import BaseMRG -from .baserandom import BaseRandom -from .basesquares import BaseSquares -from .basewell import BaseWELL -from .basexoroshiro import BaseXoroshiro -from .cwg64 import Cwg64 -from .cwg128_64 import Cwg128_64 -from .cwg128 import Cwg128 -from .fastrand32 import FastRand32 -from .fastrand63 import FastRand63 -from .lfib78 import LFib78 -from .lfib116 import LFib116 -from .lfib668 import LFib668 -from .lfib1340 import LFib1340 -from .melg607 import Melg607 -from .melg19937 import Melg19937 -from .melg44497 import Melg44497 -from .mrg287 import Mrg287 -from .mrg1457 import Mrg1457 -from .mrg49507 import Mrg49507 -from .pcg64_32 import Pcg64_32 -from .pcg128_64 import Pcg128_64 -from .pcg1024_32 import Pcg1024_32 -from .squares32 import Squares32 -from .squares64 import Squares64 -from .well512a import Well512a -from .well1024a import Well1024a -from .well19937c import Well19937c -from .well44497b import Well44497b -from .xoroshiro256 import Xoroshiro256 -from .xoroshiro512 import Xoroshiro512 -from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib-org/annotation_types.py b/PyRandLib-org/annotation_types.py deleted file mode 100644 index e097004..0000000 --- a/PyRandLib-org/annotation_types.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Tuple, Union - -Numerical = Union[ int, float ] -StatesList = Union[ Tuple[int], List[int] ] -StatesListAndExt = Tuple[ StatesList, int ] -StateType = Union[ StatesList, StatesListAndExt ] -SeedStateType = Union[ Numerical, StateType ] - - -#===== end of PyRandLib.annotation_types =============================== - -# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/PyRandLib-org/basecwg.py b/PyRandLib-org/basecwg.py deleted file mode 100644 index 40c2aa5..0000000 --- a/PyRandLib-org/basecwg.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesListAndExt - - -#============================================================================= -class BaseCWG( BaseRandom ): - """Definition of the base class for all Collatz-Weyl pseudo-random Generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - CWG models are chaotic generators that are combined with Weyl sequences to - eliminate the risk of short cycles. They have a large period, a uniform - distribution, and the ability to generate multiple independent streams by - changing their internal parameters (Weyl increment). CWGs owe their - exceptional quality to the arithmetical dynamics of noninvertible, - generalized, Collatz mappings based on the wellknown Collatz conjecture. - There is no jump function, but each odd number of the Weyl increment - initiates a new unique period, which enables quick initialization of - independent streams (this text is extracted from [8], see README.md). - - The internal implementation of the CWG algorithm varies according to its - implemented version. See implementation classes to get their formal - description. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with low computation time, medium period, 64- bits output values and very - good randomness characteristics. - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> StatesListAndExt: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For CWG, this state is defined by a list of control values - (a, weyl and s - or a list of 4 coeffs) and an internal state - value, which are used in methods 'next() and 'setstate() of - every inheriting class. - - All inheriting classes MUST IMPLEMENT this method. - """ - return (self._a, self._weyl, self._s, self._state) - - -#===== end of module basecwg.py ======================================== diff --git a/PyRandLib-org/baselcg.py b/PyRandLib-org/baselcg.py deleted file mode 100644 index acd6ce2..0000000 --- a/PyRandLib-org/baselcg.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical - - -#============================================================================= -class BaseLCG( BaseRandom ): - """Definition of the base class for all LCG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the evaluation - done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in - 'TestU01: A C Library for Empirical Testing of Random Number Generators - - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August - 2007'. It is not recommended to use such pseudo-random numbers generators - for serious simulation applications. - - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low - computation time but shorter period and worse randomness characteristics - than for FastRand63. - See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with - low computation time also, longer period and quite better randomness - characteristics than for FastRand32. - - Furthermore this class is callable: - rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> int: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For LCG, the state is defined with a single integer, 'self._state', - which has to be used in methods 'next() and 'setstate() of every - inheriting class. - """ - return self._state - -#===== end of module baselcg.py ======================================== diff --git a/PyRandLib-org/baselfib64.py b/PyRandLib-org/baselfib64.py deleted file mode 100644 index 96da67d..0000000 --- a/PyRandLib-org/baselfib64.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseLFib64( BaseRandom ): - """The base class for all LFib PRNG based on 64-bits numbers. - - Definition of the base class for all LFib pseudo-random generators based - on 64-bits generated numbers. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^(bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. - 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and - 2.4e+403 periods) while same computation time and far higher precision (64-bits - calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 - integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an - example. - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an - index in this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module baselfib64.py ===================================== - diff --git a/PyRandLib-org/basemelg.py b/PyRandLib-org/basemelg.py deleted file mode 100644 index e69665b..0000000 --- a/PyRandLib-org/basemelg.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseMELG( BaseRandom ): - """Definition of the base class for all MELG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: while the WELL algorithm use 32-bits integers as their internal state and - output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. This is the shortest period version proposed in paper [11]. - See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for - an example. - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE 64-bits integers, an index in this list and an - additional 64-bits integer as a state extension. Should _seedState - be a sole integer or float then it is used as initial seed for the - random filling of the internal state of the PRNG. Should _seedState - be anything else (e.g. None) then the shuffling of the local - current time value is used as such an initial seed. - - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE 64-bits integers, an index - in this list and an additional 64-bits integer as a state extension. - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - Should _seedstate not contain a list of self._STATE_SIZE 64- - bits integers, a value for attribute self._index and a value - for attribute self._extState, this method tries its best to - initialize all these values. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - elif count == 2: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: StateType = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module basemelg.py ======================================= diff --git a/PyRandLib-org/basemrg.py b/PyRandLib-org/basemrg.py deleted file mode 100644 index 8059c8e..0000000 --- a/PyRandLib-org/basemrg.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseMRG( BaseRandom ): - """Definition of the base class for all MRG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (32-bits modulus) but use of more memory space (1597 - integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. - See Mrg287 for an example. - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for the - random filling of the internal list of self._STATE_SIZE integers. - Should _seedState be anything else (e.g. None) then the shuffling - of the local current time value is used as such an initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in - this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] - - -#===== end of module basemrg.py ======================================== diff --git a/PyRandLib-org/basepcg.py b/PyRandLib-org/basepcg.py deleted file mode 100644 index 8c9a423..0000000 --- a/PyRandLib-org/basepcg.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical - - -#============================================================================= -class BasePCG( BaseRandom ): - """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low - computation time and medium period, with 2 32-bits word integers memory - consumption. Output values are returned on 32 bits. - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> int: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For LCG, the state is defined with a single integer, 'self._value', - which has to be used in methods 'random() and 'setstate() of every - inheriting class. - """ - return self._state - -#===== end of module basepcg.py ======================================== diff --git a/PyRandLib-org/baserandom.py b/PyRandLib-org/baserandom.py deleted file mode 100644 index 12b8598..0000000 --- a/PyRandLib-org/baserandom.py +++ /dev/null @@ -1,401 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from random import Random -from typing import List, Tuple, Union - -from .annotation_types import Numerical, SeedStateType, StateType - - -#============================================================================= -class BaseRandom( Random ): - """This is the base class for all pseudo-random numbers generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a - 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with - very low memory consumption (resp. 1 and 2 32-bits integers). - - See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib - generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, - 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher - precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and - 1279 32-bits integers). - - See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 32-bits integers memory consumption. - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (32-bits 47 integers). - See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low - computation time too (31-bits modulus) but use of more memory space (1597 - 32-bits integers). - - See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low - computation time, and for very low memory consumption for the two first (resp. 4, - 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, - i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump - ahead features. Since they all are exposing only a part of their internal state, - they are difficult to reverse and to predict. - - See Well512a, Well1024a, Well19937c and Well44479b for large to very large period - generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, - 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation - times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, - 624 and 1391 32-bits integers. - - Python built-in class random.Random is subclassed here to use a different basic - generator of our own devising: in that case, overriden methods are: - - random(), seed(), getstate(), and setstate(). - - Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method - next(). - - Furthermore this class and all its inheriting sub-classes are callable. Example: - rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Please notice that for simulating the roll of a dice you should program: - diceRoll = UFastRandom() - print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. - Such a programming is a simplified while still robust emulation of inherited - methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). - - Inheriting from random.Random, next methods are also available: - | - | betavariate(self, alpha, beta) - | Beta distribution. - | - | Conditions on the parameters are alpha > 0 and beta > 0. - | Returned values range between 0 and 1. - | - | - | choice(self, seq) - | Choose a random element from a non-empty sequence. - | - | - | expovariate(self, lambd) - | Exponential distribution. - | - | lambd is 1.0 divided by the desired mean. It should be - | nonzero. (The parameter would be called "lambda", but that is - | a reserved word in Python.) Returned values range from 0 to - | positive infinity if lambd is positive, and from negative - | infinity to 0 if lambd is negative. - | - | - | gammavariate(self, alpha, beta) - | Gamma distribution. Not the gamma function! - | - | Conditions on the parameters are alpha > 0 and beta > 0. - | - | - | gauss(self, mu, sigma) - | Gaussian distribution. - | - | mu is the mean, and sigma is the standard deviation. This is - | slightly faster than the normalvariate() function. - | - | Not thread-safe without a lock around calls. - | - | - | getrandbits(self, k) - | Returns a non-negative Python integer with k random bits. - | Changed since version 3.9: This method now accepts zero for k. - | - | - | getstate(self) - | Return internal state; can be passed to setstate() later. - | - | - | lognormvariate(self, mu, sigma) - | Log normal distribution. - | - | If you take the natural logarithm of this distribution, you'll get a - | normal distribution with mean mu and standard deviation sigma. - | mu can have any value, and sigma must be greater than zero. - | - | - | normalvariate(self, mu, sigma) - | Normal distribution. - | - | mu is the mean, and sigma is the standard deviation. - | - | - | paretovariate(self, alpha) - | Pareto distribution. alpha is the shape parameter. - | - | - | randbytes(self, n) - | Generate n random bytes. - | This method should not be used for generating security tokens. - | Notice: this method has been added in Python 3.9. It is implemented - | in PyRandLib for former versions of the language also. - | - | - | randint(self, a, b) - | Return random integer in range [a, b], including both end points. - | - | - | randrange(self, start, stop=None, step=1, int=) - | Choose a random item from range(start, stop[, step]). - | - | This fixes the problem with randint() which includes the - | endpoint; in Python this is usually not what you want. - | - | Do not supply the 'int' argument. - | - | - | sample(self, population, k) - | Chooses k unique random elements from a population sequence or set. - | - | Returns a new list containing elements from the population while - | leaving the original population unchanged. The resulting list is - | in selection order so that all sub-slices will also be valid random - | samples. This allows raffle winners (the sample) to be partitioned - | into grand prize and second place winners (the subslices). - | - | Members of the population need not be hashable or unique. If the - | population contains repeats, then each occurrence is a possible - | selection in the sample. - | - | To choose a sample in a range of integers, use range as an argument. - | This is especially fast and space efficient for sampling from a - | large population: sample(range(10000000), 60) - | - | - | seed(self, a=None, version=2) - | Initialize internal state from hashable object. - | - | None or no argument seeds from current time or from an operating - | system specific randomness source if available. - | - | For version 2 (the default), all of the bits are used if *a *is a str, - | bytes, or bytearray. For version 1, the hash() of *a* is used instead. - | - | If *a* is an int, all bits are used. - | - | - | setstate(self, state) - | Restore internal state from object returned by getstate(). - | - | - | shuffle(self, x, random=None, int=) - | x, random=random.random -> shuffle list x in place; return None. - | - | Optional arg random is a 0-argument function returning a random - | float in [0.0, 1.0); by default, the standard random.random. - | - | - | triangular(self, low=0.0, high=1.0, mode=None) - | Triangular distribution. - | - | Continuous distribution bounded by given lower and upper limits, - | and having a given mode value in-between. - | - | http://en.wikipedia.org/wiki/Triangular_distribution - | - | - | uniform(self, a, b) - | Get a random number in the range [a, b) or [a, b] depending on rounding. - | - | - | vonmisesvariate(self, mu, kappa) - | Circular data distribution. - | - | mu is the mean angle, expressed in radians between 0 and 2*pi, and - | kappa is the concentration parameter, which must be greater than or - | equal to zero. If kappa is equal to zero, this distribution reduces - | to a uniform random angle over the range 0 to 2*pi. - | - | - | weibullvariate(self, alpha, beta) - | Weibull distribution. - | - | alpha is the scale parameter and beta is the shape parameter. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 32 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seed: SeedStateType = None) -> None: - """Constructor. - - Should _seed be None or not a number then the local time is used - (with its shuffled value) as a seed. - - Notice: the Python built-in base class random.Random internally - calls method setstate() which MUST be overridden in classes that - inherit from class BaseRandom. - """ - super().__init__( _seed ) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - - This is the core of the PRNGs. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def random(self) -> float: - """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). - - Inheriting classes MUST OVERRIDE the value of the base class constant - attribute _NORMALIZE when the random integer values they return are - coded on anything else than 32 bits. - """ - return self.next() * self._NORMALIZE - - - #------------------------------------------------------------------------- - def getrandbits(self, k: int) -> int: - """Returns k bits from the internal state of the generator. - - k must be a positive value greater or equal to zero. - """ - assert k >= 0, "the returned bits count must not be negative" - assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" - - return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) - - - #------------------------------------------------------------------------- - def randbytes(self, n: int) -> bytes: - """Generates n random bytes. - - This method should not be used for generating security tokens. - (use Python built-in secrets.token_bytes() instead) - """ - assert n >= 0 # and self._OUT_BITS >= 8 - return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can then be passed to setstate() to restore the state. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def setstate(self, _state: StateType) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call to getstate(), - and setstate() restores the internal state of the generator to what - it was at the time setstate() was called. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def seed(self, _seed: SeedStateType = None) -> None: - """Initiates the internal state of this pseudo-random generator. - """ - try: - self.setstate( _seed ) - except: - super().seed( _seed ) - - - #------------------------------------------------------------------------- - def __call__(self, _max : Union[Numerical, - Tuple[Numerical], - List[Numerical]] = 1.0, - times: int = 1 ) -> Union[Numerical, List[Numerical]]: - """This class's instances are callable. - - The returned value is uniformly contained within the - interval [0.0 : _max]. When times is set, a list of - iterated pseudo-random values is returned. 'times' - must be an integer. If less than 1 it is forced to - be 1. - '_max' may be a list or a tuple of values, in which - case a list of related pseudo-random values is - returned with entries of the same type than the same - indexed entry in '_max'. - """ - assert isinstance( times, int ) - if times < 1: - times = 1 - - if isinstance( _max, int ): - ret = [ int(_max * self.random()) for _ in range(times) ] - elif isinstance( _max, float ): - ret = [ _max * self.random() for _ in range(times) ] - else: - try: - if times == 1: - ret = [ self(m,1) for m in _max ] - else: - ret = [ [self(m,1) for m in _max] for _ in range(times) ] - except: - ret = [ self.__call__(times=1) ] - - return ret[0] if len(ret) == 1 else ret - - - #------------------------------------------------------------------------- - @classmethod - def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: - """Returns the value of a left rotating by _rotCount bits - - Useful for some inheriting classes. - """ - #assert 1 <=_rotCount <= _bitsCount - loMask = (1 << (_bitsCount - _rotCount)) - 1 - hiMask = ((1 << _bitsCount) - 1) ^ loMask - hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) - return ((_value & loMask) << _rotCount) | hiBits - - -#===== end of module baserandom.py ===================================== diff --git a/PyRandLib-org/basesquares.py b/PyRandLib-org/basesquares.py deleted file mode 100644 index 2968237..0000000 --- a/PyRandLib-org/basesquares.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesList -from .splitmix import SplitMix32 - - -#============================================================================= -class BaseSquares( BaseRandom ): - """Definition of the base class for the Squares counter-based pseudo-random Generator. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Squares models are based on an incremented counter and a key. The - algorithm squares a combination of the counter and the key values, - and exchanges the upper and lower bits of the combination, the - whole repeated a number of times (4 to 5 rounds). Output values - are provided on 32-bits or on 64-bits according to the model. See - [9] in README.md. - - See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 32-bits output values and - very good randomness characteristics. - - See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 64-bits output values and - very good randomness characteristics. Caution: the 64-bits version - should not pass the birthday test, which is a randmoness issue, - while this is not mentionned in the original paper (see [9] in - file README.md). - - Furthermore this class is callable: - rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> StatesList: - """Returns an object capturing the current internal state of the generator. - """ - return (self._counter, self._key) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType) -> None: - """Restores or sets the internal state of the generator. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._counter = 0 - self._key = self._initKey( _state ) - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - self._counter = 0 - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) - else: - self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) - - else: - try: - self._counter = _state[0] & 0xffff_ffff_ffff_ffff - self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd - except: - # uses local time as initial seed - self._counter = 0 - self._key = self._initKey() - - - #------------------------------------------------------------------------- - def _initKey(self, _seed: int = None) -> int: - """Initalizes the attribute _key according to the original recommendations - see [9]. - """ - hexDigits = [ i for i in range(1, 16) ] - key = 0 - - initRand = SplitMix32( _seed ) - - # 8 high hexa digits - all different - n = 15 - while n >= 8: - k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class - h = hexDigits[ k ] - key <<= 4 - key += h - n -= 1 - if k < n: - hexDigits[ k ] = hexDigits[ n ] - hexDigits[ n ] = h - - # 8 low hexa digits - all different - n = 15 - while n >= 8: - k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class - h = hexDigits[ k ] - key <<= 4 - key += h - n -= 1 - if k < n: - hexDigits[ k ] = hexDigits[ n ] - hexDigits[ n ] = h - - return key | 1 # Notice: key must be odd - - -#===== end of module basesquares.py ==================================== diff --git a/PyRandLib-org/basewell.py b/PyRandLib-org/basewell.py deleted file mode 100644 index b7dc2ac..0000000 --- a/PyRandLib-org/basewell.py +++ /dev/null @@ -1,307 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StateType -from .splitmix import SplitMix32 - - -#============================================================================= -class BaseWELL( BaseRandom ): - """Definition of the base class for all WELL pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in the 4 different versions implemented here has been coded - here as a direct implementation of their descriptions in the initial paper - "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François - PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO - (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, - No. 1, March 2006, Pages 1–16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - So, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory little consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for - an example. - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937b generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for the - random filling of the internal list of self._STATE_SIZE integers. - Should _seedState be anything else (e.g. None) then the shuffling - of the local current time value is used as such an initial seed. - - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in - this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: StateType = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix32( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - - #------------------------------------------------------------------------- - @classmethod - def _M0(cls, x: int = None) -> int: - return 0 - - #------------------------------------------------------------------------- - @classmethod - def _M1(cls, x: int) -> int: - return x - - #------------------------------------------------------------------------- - @classmethod - def _M2_pos(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x >> t - - #------------------------------------------------------------------------- - @classmethod - def _M2_neg(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return (x << t) & 0xffff_ffff - - #------------------------------------------------------------------------- - @classmethod - def _M3_pos(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x ^ (x >> t) - - #------------------------------------------------------------------------- - @classmethod - def _M3_neg(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x ^ ((x << t) & 0xffff_ffff) - - #------------------------------------------------------------------------- - @classmethod - def _M4(cls, x: int, a: int) -> int: - #assert 0 <= a <= 0xffff_ffff - return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 - - #------------------------------------------------------------------------- - @classmethod - def _M5_pos(cls, x: int, t: int, a: int) -> int: - #assert 0 <= t < 32 - #assert 0 <= b <= 0xffff_ffff - return x ^ ((x >> t) & a) - - #------------------------------------------------------------------------- - @classmethod - def _M5_neg(cls, x: int, t: int, a: int) -> int: - #assert 0 <= t < 32 - #assert 0 <= a <= 0xffff_ffff - return x ^ ((x << t) & a) - - #------------------------------------------------------------------------- - @classmethod - def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: - #assert 0 <= q < 32 - #assert 0 <= t < 32 - #assert 0 <= s < 32 - #assert 0 <= a <= 0xffff_ffff - y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) - return y ^ a if x & (1 << t) else y - - #------------------------------------------------------------------------- - @classmethod - def _d(cls, s: int) -> int: - #assert 0 <= s < 32 - return 0xffff_ffff ^ (1 << s) - - #------------------------------------------------------------------------- - @classmethod - def _tempering(cls, x: int, b: int, c: int) -> int: - #assert 0 <= b <= 0xffff_ffff - #assert 0 <= c <= 0xffff_ffff - #assert 0 <= w <= 32 - # notice: the generic algorithm truncs x on w-bits. All of the implemented - # ones in PyRandLib are set on 32-bits. So, no truncation takes place here - x = x ^ (((x << 7) & 0xffff_ffff) & b) - return x ^ (((x << 15) & 0xffff_ffff) & c) - - #------------------------------------------------------------------------- - @property - def _a1(self): - return 0xda44_2d24 - - @property - def _a2(self): - return 0xd3e4_3ffd - - @property - def _a3(self): - return 0x8bdc_b91e - - @property - def _a4(self): - return 0x86a9_d87e - - @property - def _a5(self): - return 0xa8c2_96d1 - - @property - def _a6(self): - return 0x5d6b_45cc - - @property - def _a7(self): - return 0xb729_fcec - -#===== end of module basewell.py ======================================= diff --git a/PyRandLib-org/basexoroshiro.py b/PyRandLib-org/basexoroshiro.py deleted file mode 100644 index 1226391..0000000 --- a/PyRandLib-org/basexoroshiro.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Union - -from .baserandom import BaseRandom -from .annotation_types import Numerical, StatesList, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseXoroshiro( BaseRandom ): - """The base class for all xoroshiro PRNGs. - - Definitiion of the base class for all versions of the xoroshiro algorithm - implemented in PyRandLib. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number - Generators. The xoroshiro linear transformation updates cyclically two words of a - larger state array. The base xoroshiro linear transformation is obtained combining - a rotation, a shift, and again a rotation. - (extracted from the original paper, see [10] in file README.md) - - An addition or a multiplication operation is internally applied also to the state - of the PRNGs. Doubling the same operation has proven to enhance then randomness - quality of the PRNG. This is the model of the algorithms that is implemeted in - PyRandLib. - - The implemented algorithms shortly escape from the zeroland (10 to 100 calls are - enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 - version of the algorithm has nevertheless shown close repeats flaws, with a bad - Hamming weight near zero. Xoroshiro512 seems to best fit this property. - (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). - - See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. - 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 - periods), 64-bits precision calculations and short memory consumption (resp. 8, - 16 and 32 integers coded on 64 bits. - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 - for an example. - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO: int = (1 << 64) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> List[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return self._state[:] - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module basexoroshiro.py ================================== - diff --git a/PyRandLib-org/cwg128.py b/PyRandLib-org/cwg128.py deleted file mode 100644 index 22fbab2..0000000 --- a/PyRandLib-org/cwg128.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg128( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 128-bits calculations and 128-bits output values with large - period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG - algorithms offer multi streams features, by simply using different initial - settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 96 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - Notice: in the original paper, four control value c[0] to c[3] are used. - It appears that these value are used just are 's' for c[0], 'x' for c[1], - 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg128() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 128 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO: int = (1 << 128) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & self._MODULO - self._weyl = (self._weyl + self._s) & self._MODULO - self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO - # returns the xored-shifted output value - return self._state ^ (self._a >> 96) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits - self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd - - else: - try: - self._a = _state[0] & self._MODULO - self._weyl = _state[1] & self._MODULO - self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd - self._state = _state[3] & self._MODULO - - except: - # uses local time as initial seed - self.setstate() - -#===== end of module cwg128.py ========================================= diff --git a/PyRandLib-org/cwg128_64.py b/PyRandLib-org/cwg128_64.py deleted file mode 100644 index 5f7a936..0000000 --- a/PyRandLib-org/cwg128_64.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg128_64( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 128-bits calculations and 64-bits output values with small - period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG - algorithms offer multi streams features, by simply using different - initial settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 48 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg128_64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff - self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff - self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff - # returns the xored-shifted output value - return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._s = initRand() | 1; # Notice: must be odd - self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits - - else: - try: - self._a = _state[0] & 0xffff_ffff_ffff_ffff - self._weyl = _state[1] & 0xffff_ffff_ffff_ffff - self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd - self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits - - except: - # uses local time as initial seed - self.setstate() - - -#===== end of module cwg128_64.py ====================================== diff --git a/PyRandLib-org/cwg64.py b/PyRandLib-org/cwg64.py deleted file mode 100644 index 7b34c77..0000000 --- a/PyRandLib-org/cwg64.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg64( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 64-bits calculations and 64-bits output values with small - period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG - algorithms offer multi streams features, by simply using different - initial settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 48 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff - self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff - self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff - # returns the xored-shifted output value - return self._state ^ (self._a >> 48) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._s = initRand() | 1; # Notice: must be odd - self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits - - else: - try: - self._a = _state[0] & 0xffff_ffff_ffff_ffff - self._weyl = _state[1] & 0xffff_ffff_ffff_ffff - self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd - self._state = _state[3] & 0xffff_ffff_ffff_ffff - - except: - # uses local time as initial seed - self.setstate() - - -#===== end of module cwg64.py ========================================== diff --git a/PyRandLib-org/fastrand32.py b/PyRandLib-org/fastrand32.py deleted file mode 100644 index 9813c66..0000000 --- a/PyRandLib-org/fastrand32.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselcg import BaseLCG -from .annotation_types import Numerical -from .splitmix import SplitMix32 - - -#============================================================================= -class FastRand32( BaseLCG ): - """ - Pseudo-random numbers generator - Linear Congruential Generator dedicated - to 32-bits calculations with very short period (about 4.3e+09) but very - short time computation. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. - - The implementation of this LCG 32-bits model is based on (a=69069, c=1) - since these two values have evaluated to be the 'best' ones for LCGs - within TestU01 while m = 2^32. - - See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with - low computation time also, longer period and quite better randomness - characteristics than for FastRand32. - - Furthermore this class is callable: - rand = FastRand32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = FastRand32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff - return self._state - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix32( _state ) - self._state = initRand() - else: - initRand = SplitMix32() - self._state = initRand() - -#===== end of module fastrand32.py ===================================== diff --git a/PyRandLib-org/fastrand63.py b/PyRandLib-org/fastrand63.py deleted file mode 100644 index 4483c40..0000000 --- a/PyRandLib-org/fastrand63.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselcg import BaseLCG -from .annotation_types import Numerical -from .splitmix import SplitMix63 - - -#============================================================================= -class FastRand63( BaseLCG ): - """ - Pseudo-random numbers generator - Linear Congruential Generator dedicated - to 63-bits calculations with very short period (about 9.2e+18) and short - time computation. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. - - The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) - since these two values have evaluated to be the 'best' ones for LCGs - within TestU01 while m = 2^63. - - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low - computation time but shorter period and worse randomness characteristics - than for FastRand63. - - Furthermore this class is callable: - rand = FastRand63() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = FastRand63() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 63 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff - return self._state - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix63( _state ) - self._state = initRand() - else: - initRand = SplitMix63() - self._state = initRand() - - -#===== end of module fastrand63.py ===================================== diff --git a/PyRandLib-org/lfib116.py b/PyRandLib-org/lfib116.py deleted file mode 100644 index 8f1f000..0000000 --- a/PyRandLib-org/lfib116.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib116( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (8.3e+34). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = (x(i-24) + x(i-55)) mod 2^64 - - and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due - to the use of a 2^64 modulo and little memory space consumption (55 long integ- - ers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, - 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 17, 607 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib116() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib116() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-5 and i-17 -th values - k24 = self._index-24 - if k24 < 0: - k24 += LFib116._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib116._STATE_SIZE - - return myValue - -#===== end of module lfib116.py ======================================== diff --git a/PyRandLib-org/lfib1340.py b/PyRandLib-org/lfib1340.py deleted file mode 100644 index 5007045..0000000 --- a/PyRandLib-org/lfib1340.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib1340( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (2.4e+403). - - This module is part of library PyRandLib. - - Copyright (c) 2017-2025 Philippe Schmouker - - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = (x(i-861) + x(i-1279)) mod 2^64 - - and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time - due to the use of a 2^64 modulo but memory space consumption (1379 long integ- - ers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, - 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while - same computation time and far higher precision (64-bits calculations) than MRGs, - but memory consumption (resp. 17, 55 and 607 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib1340() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib1340() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-861 and i-1279 -th values - k861 = self._index-861 - if k861 < 0: - k861 += LFib1340._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib1340._STATE_SIZE - - return myValue - -#===== end of module lfib1340.py ====================================== diff --git a/PyRandLib-org/lfib668.py b/PyRandLib-org/lfib668.py deleted file mode 100644 index 0f5e494..0000000 --- a/PyRandLib-org/lfib668.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib668( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (1.2e+201). - - This module is part of library PyRandLib. - - Copyright (c) 2017-2025 Philippe Schmouker - - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = ( x(i-273) + x(i-607) ) mod 2^64 - - and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due - to the use of a 2^64 modulo but memory space consumption (607 long integers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, - 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 17, 55 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib668() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib668() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-273 and i-607 -th values - k273 = self._index-273 - if k273 < 0: - k273 += LFib668._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib668._STATE_SIZE - - return myValue - -#===== end of module lfib668.py ======================================= diff --git a/PyRandLib-org/lfib78.py b/PyRandLib-org/lfib78.py deleted file mode 100644 index 7f3589c..0000000 --- a/PyRandLib-org/lfib78.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib78( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (3.0e+23). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = ( x(i-5) + x(i-17) ) mod 2^64 - - and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due - to the use of a 2^64 modulo and few memory space consumption (17 long integers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, - 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 55, 607 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib78() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib78() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-5 and i-17 -th values - k5 = self._index-5 - if k5 < 0: - k5 += LFib78._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib78._STATE_SIZE - - return myValue - -#===== end of module lfib78.py ========================================= diff --git a/PyRandLib-org/melg19937.py b/PyRandLib-org/melg19937.py deleted file mode 100644 index 5d9b093..0000000 --- a/PyRandLib-org/melg19937.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg19937( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Furthermore, this class is callable: - rand = Melg19937() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg19937() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 312 - _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 311 - self._index = i_1 - - s311 = self._state[311] - - x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) - self._state[311] = s311 - - si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) - return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib-org/melg44497.py b/PyRandLib-org/melg44497.py deleted file mode 100644 index 286cdd7..0000000 --- a/PyRandLib-org/melg44497.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg44497( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. - See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - - Furthermore, this class is callable: - rand = Melg444907() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg444907() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 696 - _A_COND = (0, 0x4fa9_ca36_f293_c9a9) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 695 - self._index = i_1 - - s695 = self._state[695] - - x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) - self._state[695] = s695 - - si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) - return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib-org/melg607.py b/PyRandLib-org/melg607.py deleted file mode 100644 index 6a38a55..0000000 --- a/PyRandLib-org/melg607.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg607( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Furthermore, this class is callable: - rand = Melg607() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg607() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 - _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 9 - self._index = i_1 - - s9 = self._state[9] - - x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) # M=5, s1=13 - self._state[9] = s9 - - si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) # s2=35 - return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) # s3=30, L=3, b = 0x66ed... - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib-org/mrg1457.py b/PyRandLib-org/mrg1457.py deleted file mode 100644 index 17f8c32..0000000 --- a/PyRandLib-org/mrg1457.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg1457( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with long period (3.98e+438). - - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random - generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence - - x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation - time. - - See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (31-bits modulus) but use of more memory space (1597 - integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg1457() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers - _MODULO : int = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 31 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # The DX-47-3 version uses the recurrence - # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values - k1 = self._index-1 - if k1 < 0: - k1 = Mrg1457._STATE_SIZE - 1 - - k24 = self._index-24 - if k24 < 0: - k24 += Mrg1457._STATE_SIZE - - # then evaluates current value - myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 - self._state[self._index] = myValue - - # next index - self._index = (self._index + 1) % Mrg1457._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib-org/mrg287.py b/PyRandLib-org/mrg287.py deleted file mode 100644 index 119c3f0..0000000 --- a/PyRandLib-org/mrg287.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg287( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive - Generator with a long period (2.49e+86). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 32-bits model is based on a Lagged Fibonacci - generator (LFIB), the Marsa-LFIB4 one. - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) paper. - - The Marsa-LIBF4 version uses the recurrence - - x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due - to the use of a 2^32 modulo. - - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (31-bits modulus) but use of more memory space (1_597 - integers). - - Furthermore, this class is callable: - rand = Mrg287() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg287() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO : int = 0xffff_ffff - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # The Marsa-LIBF4 version uses the recurrence - # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values - k55 = self._index-55 - if k55 < 0: - k55 += Mrg287._STATE_SIZE - - k119 = self._index-119 - if k119 < 0: - k119 += Mrg287._STATE_SIZE - - k179 = self._index-179 - if k179 < 0: - k179 += Mrg287._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % self._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib-org/mrg49507.py b/PyRandLib-org/mrg49507.py deleted file mode 100644 index 2eb2597..0000000 --- a/PyRandLib-org/mrg49507.py +++ /dev/null @@ -1,141 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg49507( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with a very long period (1.17e+14_903). - - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It - uses the recurrence - - x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) - - and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low - computation time. - - See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - rand = Mrg49507() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg49507() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers - _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 31 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-7, i-1597 -th values - k7 = self._index-7 - if k7 < 0: - k7 += Mrg49507._STATE_SIZE - - # then evaluates current value - myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % Mrg49507._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand49507.py =================================== diff --git a/PyRandLib-org/pcg1024_32.py b/PyRandLib-org/pcg1024_32.py deleted file mode 100644 index c721499..0000000 --- a/PyRandLib-org/pcg1024_32.py +++ /dev/null @@ -1,280 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .annotation_types import Numerical, SeedStateType, StateType -from .pcg64_32 import Pcg64_32 -from .splitmix import SplitMix32 - - -#============================================================================= -class Pcg1024_32( Pcg64_32 ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - extended by 1,024 equistributed generators, dedicated to 64-bits - calculations and 32-bits output with very large period (about 6.53e+9882) - but very short time computation. This version of the PCG algorithm gets - the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers - jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version - of th e PCG algorithm, as specified in the related paper (see [7] in - document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 - and the additional permutation output function and its internal multiple - states that implements its 1024-dimensionally equidistributed generator - directly coded in method 'next()'. - - See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low - computation time also and a longer period than for Pcg64_32, with 2 - 32-bits word integers memory consumption. Output values are returned on - 32 bits. - - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg1024_32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg1024_32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - _EXTENDED_STATE_SIZE: int = 1024 - - - #------------------------------------------------------------------------- - def __init__(self, _seed: SeedStateType = None) -> None: - """Constructor. - - Should _seed be None or not be of SeedStateType then the - local time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates a to-be-xor'ed 32-bits value from current extended state - if self._state & 0xffff_ffff == 0: - self._advancetable() - extendedValue = self._extendedState[ self._state & 0x03ff ] - - # then xor's it with the next 32-bits value evaluated with the internal state - return super().next() ^ extendedValue - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a list that contains self._STATE_SIZE integers. - """ - return [ self._extendedState[:], self._state ] - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: SeedStateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState ) - - elif count == 2: - # each entry in _seedState[0] MUST be a 32-bits integer - # _seedState[1] MUST be a 64-bits integer - extendedCount = len( _seedState[0] ) - if extendedCount == self._EXTENDED_STATE_SIZE: - self._extendedState = _seedState[0][:] - self._state = _seedState[1] - elif extendedCount > 0: - extended = _seedState[0] - for s in _seedState[1:]: - extended ^= s - self._initextendedstate( extended ) - self._state = _seedState[1] - else: - self._initstate( _seedState[1] ) - - else: - self._initstate() - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _advancetable(self) -> None: - """Advances the extended states - """ - carry = False - for i, s in enumerate( self._extendedState ): - if carry: - carry = self._extendedstep(s, i) - if self._extendedstep(s, i): # notice: must be evaluated before carry is set - carry = True - - - #------------------------------------------------------------------------- - def _extendedstep(self, value: int, i: int) -> bool: - """Evaluates new extended state indexed value in the extended state table. - - Returns True when the evaluated extended value is set to zero on all bits - but its two lowest ones - these two bits never change with MCGs. - """ - state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff - state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) - state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff - - result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) - result ^= result >> 22 - - self._extendedState[i] = result - - return result == state & 0b11 - - - #------------------------------------------------------------------------- - def _initextendedstate(self, _initialSeed: Numerical = None) -> None: - """Inits the extended list of values. - - Inits the extended list of values according to some initial - seed that has to be an integer, or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed. - initRand = SplitMix32( _initialSeed ) - self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal state of this PRNG. - - Inits its current state and its extended state also. The - initial seed has to be an integer, or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as the initial seed value. - The same initial seed is finally used to seed the current - state and the extended state. To avoid any unexpected - correlation between current state and any value of the - extended one, we use different PRNGs to seed the internal - state on one side and the extended state on the other side. - """ - super().setstate( _initialSeed ) # uses Pcg64_32() - self._initextendedstate( _initialSeed ) # uses Well1024a() - - - #------------------------------------------------------------------------- - @classmethod - def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: - """Evaluates the inversion of an xor-shift operation. - """ - if shift * 2 >= bitsCount: - return value ^ (value >> shift) - - botMask = (1 << (bitsCount - shift * 2)) - 1 - topMask = ~botMask & 0xffff_ffff - - top = (value ^ (value >> shift)) & topMask - - newBitsShift = bitsCount - shift - bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask - - return top | bot - - -#===== end of module pcg1024_32.py ===================================== diff --git a/PyRandLib-org/pcg128_64.py b/PyRandLib-org/pcg128_64.py deleted file mode 100644 index 6f9eb84..0000000 --- a/PyRandLib-org/pcg128_64.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basepcg import BasePCG -from .annotation_types import Numerical -from .splitmix import SplitMix64 - - -#============================================================================= -class Pcg128_64( BasePCG ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - dedicated to 128-bits calculations and 64-bits output with medium period - (about 3.40e+38) but very short time computation. The PCG algorithm - offers jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of - the PCG algorithm, as specified in the related paper (see [7] in document - README.md), so with a and c values coded on 128 bits (see method next()), - the modulo m = 2^128 and the additional permutation output function - directly implemented in method 'next()'. - - See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low - computation time also and a longer period than for Pcg64_32, with 2 - 32-bits word integers memory consumption. Output values are returned on - 32 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg128_64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg128_64() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO_128 : int = (1 << 128) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - current_state = self._state - self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & self._MODULO_128 - # the permutated output is then computed - random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state - current_state ^= current_state >> 64 # fixed shift XOR is then evaluated - value = current_state & 0xffff_ffff_ffff_ffff - rot_mask = (1 << random_rotation) - 1 - return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._state = _state & self._MODULO_128 - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._state = int( _state + 0.5 ) & self._MODULO_128 - else: - self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 - - else: - # uses local time as initial seed - initRand = SplitMix64() - self._state = (initRand() << 64) | initRand() - -#===== end of module pcg128_64.py ====================================== diff --git a/PyRandLib-org/pcg64_32.py b/PyRandLib-org/pcg64_32.py deleted file mode 100644 index 1fa867e..0000000 --- a/PyRandLib-org/pcg64_32.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basepcg import BasePCG -from .annotation_types import Numerical -from .splitmix import SplitMix64 - - -#============================================================================= -class Pcg64_32( BasePCG ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - dedicated to 64-bits calculations and 32-bits output with medium period - (about 1.84e+19) but very short time computation. The PCG algorithm - offers jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the - PCG algorithm, as specified in the related paper (see [7] in document - README.md), so with a = 6364136223846793005, c = 1442695040888963407, the - modulo m = 2^64 and the additional permutation output function directly - implemented in method 'next()'. - - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg64_32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg64_32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - current_state = self._state - self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff - # the permutated output is then computed - random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state - current_state ^= current_state >> 22 # fixed shift XOR is then evaluated - return (current_state >> (22 + random_shift)) & 0xffff_ffff - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._state = _state & 0xffff_ffff_ffff_ffff - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff - else: - self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff - - else: - # uses local time as initial seed - initRand = SplitMix64() - self._state = initRand() - -#===== end of module pcg64_32.py ======================================= diff --git a/PyRandLib-org/splitmix.py b/PyRandLib-org/splitmix.py deleted file mode 100644 index 2d674e8..0000000 --- a/PyRandLib-org/splitmix.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -import time - -from .annotation_types import Numerical - - -#============================================================================= -class SplitMix64: - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 64 bits. It implements the - 64-bits version of the Fast Splittable Pseudorandom Number - Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine - H. Flood in "Fast splittable pseudorandom number generators.", in - ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. - - It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) - in 2015, provided under the Creative Commons license and modified - under the same license by D. Lemire by Aug. 2017. - (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - if isinstance( _seed, int ): - self.state = self.__call__( _seed ) - - elif isinstance( _seed, float ): - # transforms passed initial seed from float to integer - if _seed < 0.0 : - _seed = -_seed - - if _seed >= 1.0: - self._state = self.__call__( round(_seed) ) - else: - self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) - - else: - # uses system local time - self._state = self.__call__( int(time.time() * 1000.0) ) - - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - if _seed is not None: - self._state = _seed & 0xffff_ffff_ffff_ffff - - self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff - self._state &= 0xffff_ffff_ffff_ffff - - z = self._state - z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff - z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff - - return z ^ (z >> 31) - - -#============================================================================= -class SplitMix63( SplitMix64 ): - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 63 bits. - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - super().__init__( _seed ) - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - # returns the 63 higher bits generated by base class operator () - return super().__call__( _seed ) >> 1 - - -#============================================================================= -class SplitMix32( SplitMix64 ): - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 32 bits. - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - super().__init__( _seed ) - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - # returns the 32 higher bits generated by base class operator () - return super().__call__( _seed ) >> 32 diff --git a/PyRandLib-org/squares32.py b/PyRandLib-org/squares32.py deleted file mode 100644 index f916093..0000000 --- a/PyRandLib-org/squares32.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basesquares import BaseSquares -from .annotation_types import SeedStateType, StatesList - - -#============================================================================= -class Squares32( BaseSquares ): - """ - Pseudo-random numbers generator - Squares pseudo-random Generators - dedicated to 64-bits calculations and 32-bits output values with - small period (min 2^64, i.e. 1.84e+19) but short computation time. - All Squares algorithms offer multi streams features, by simply - using different initial settings for control value 'key'. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This Squares models is based on a four rounds of squaring and - exchanging of upper and lower bits of the successive combinations. - Output values are provided on 32-bits or on 64-bits according to - the model. See [9] in README.md. - - See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 64-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Squares32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Return a 32-bits value. - """ - self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff - - y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff - z = (y + self._key) & 0xffff_ffff_ffff_ffff - # round 1 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 2 - x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 3 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 4 - return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 - -#===== end of module squares32.py ====================================== diff --git a/PyRandLib-org/squares64.py b/PyRandLib-org/squares64.py deleted file mode 100644 index 573f2e2..0000000 --- a/PyRandLib-org/squares64.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basesquares import BaseSquares -from .annotation_types import SeedStateType, StatesList - - -#============================================================================= -class Squares64( BaseSquares ): - """ - Pseudo-random numbers generator - Squares pseudo-random Generators - dedicated to 64-bits calculations and 32-bits output values with - small period (min 2^64, i.e. 1.84e+19) but short computation time. - All Squares algorithms offer multi streams features, by simply - using different initial settings for control value 'key'. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This Squares models is based on a four rounds of squaring and - exchanging of upper and lower bits of the successive combinations. - Output values are provided on 32-bits or on 64-bits according to - the model. See [9] in README.md. - Caution: this 64-bits output values version should not pass the - birthday test, which is a randmoness issue, while this is not - mentionned in the original paper (see [9] in file README.md). - - See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 32-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Squares32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Returns a 64-bits value. - """ - self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff - - y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff - z = (y + self._key) & 0xffff_ffff_ffff_ffff - # round 1 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 2 - x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 3 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 4 - t = x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 5 - return t ^ (((x * x + y) >> 32) & 0xffff_ffff) - -#===== end of module squares64.py ====================================== diff --git a/PyRandLib-org/well1024a.py b/PyRandLib-org/well1024a.py deleted file mode 100644 index 9f92c62..0000000 --- a/PyRandLib-org/well1024a.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well1024a( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well1024a() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well1024a() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - i_1 = (i - 1) & 0x1f - - z0 = self._state[i_1] - # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great - # simplification for the implementation of the generic WELL algorithm when evaluating z0. - z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) - # notice: the transformation applied to self._state[i] for Well1024a - # is the identity which leads to simplification also - z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) - # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a - # version, the zero matrix _M0 which we suppress here for calculations optimization purpose - - self._index = i_1 - return z3 - -#===== end of module well1024a.py ====================================== diff --git a/PyRandLib-org/well19937c.py b/PyRandLib-org/well19937c.py deleted file mode 100644 index 7b836e0..0000000 --- a/PyRandLib-org/well19937c.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well19937c( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well19937c() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well19937c() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - if i >= 2: - i_1, i_2 = i - 1, i - 2 - elif i == 1: - i_1, i_2 = 0, 623 - else: - i_1, i_2 = 623, 622 - - z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) - z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) - z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) - self._index = i_1 - - return self._tempering(z3, 0xe46e1700, 0x9b868000) - -#===== end of module well19937c.py ===================================== diff --git a/PyRandLib-org/well44497b.py b/PyRandLib-org/well44497b.py deleted file mode 100644 index 0157139..0000000 --- a/PyRandLib-org/well44497b.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well44497b( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well44497b() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well44497b() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - if i >= 2: - i_1, i_2 = i - 1, i - 2 - elif i == 1: - i_1, i_2 = 0, 1390 - else: - i_1, i_2 = 1390, 1389 - - z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) - z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) - z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 - self._index = i_1 - - return self._tempering(z3, 0x93dd1400, 0xfa118000) - -#===== end of module Well44497b.py ===================================== diff --git a/PyRandLib-org/well512a.py b/PyRandLib-org/well512a.py deleted file mode 100644 index 42b14a0..0000000 --- a/PyRandLib-org/well512a.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well512a( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well512a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well512a() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well512a() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - i_1 = (i - 1) & 0xf - - z0 = self._state[i_1] - # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great - # simplification for the implementation of the generic WELL algorithm when evaluating z0. - z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) - z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) - # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a - # version, the zero matrix _M0 which we suppress here for calculations optimization purpose - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) - - self._index = i_1 - return z3 - -#===== end of module well512a.py ======================================= diff --git a/PyRandLib-org/xoroshiro1024.py b/PyRandLib-org/xoroshiro1024.py deleted file mode 100644 index 62d5cb6..0000000 --- a/PyRandLib-org/xoroshiro1024.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, SeedStateType, StatesListAndExt -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro1024( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from - zeroland (100 iterations) and passes TestU01 tests. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro1024** of the algorithm. - - See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear - PRNG, with low computation time, 64-bits output values and good randomness - characteristics. - See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Furthermore this class is callable: - rand = Xoroshiro1024() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - #------------------------------------------------------------------------- - _STATE_SIZE : int = 16 - _SIZE_MODULO: int = 0xf # optimization here, to use operand & - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - ''' - const int q = p; - const uint64_t s0 = s[p = (p + 1) & 15]; - uint64_t s15 = s[q]; - const uint64_t result_plus = s0 + s15; - const uint64_t result_plusplus = rotl(s0 + s15, R) + s15; - const uint64_t result_star = s0 * S; - const uint64_t result_starstar = rotl(s0 * S, R) * T; - s15 ^= s0; - s[q] = rotl(s0, A) ^ s15 ^ (s15 << B); - s[p] = rotl(s15, C); - ''' - previousIndex = self._index - # advances the internal state of the PRNG - self._index += 1 - self._index &= self._SIZE_MODULO - sLow = self._state[ self._index ] - sHigh = self._state[ previousIndex ] ^ sLow - self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) - self._state[ self._index ] = self._rotleft( sHigh, 36 ) - # returns the output value - return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) & self._SIZE_MODULO - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module xoroshiro1024.py ================================== - diff --git a/PyRandLib-org/xoroshiro256.py b/PyRandLib-org/xoroshiro256.py deleted file mode 100644 index 0b09b63..0000000 --- a/PyRandLib-org/xoroshiro256.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, StatesList -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro256( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from - zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats - flaws, with a bad Hamming weight near zero (see - https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro256** of the algorithm. - - See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Implementation notice: the internal state of this PRNG is coded on four integers - rather than on a list of four integers, to optimize computations time. - - Furthermore this class is callable: - rand = Xoroshiro256() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - currentS1 = self._s1 - # advances the internal state of the PRNG - self._s2 ^= self._s0 - self._s3 ^= self._s1 - self._s1 ^= self._s2 - self._s0 ^= self._s3 - self._s2 ^= (currentS1 << 17) & self._MODULO - self._s3 = self._rotleft( self._s3, 45 ) - # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._s0 = initRand() - self._s1 = initRand() - self._s2 = initRand() - self._s3 = initRand() - - -#===== end of module xoroshiro256.py =================================== - diff --git a/PyRandLib-org/xoroshiro512.py b/PyRandLib-org/xoroshiro512.py deleted file mode 100644 index 73ed1c8..0000000 --- a/PyRandLib-org/xoroshiro512.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, StatesList -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro512( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from - zeroland (30 iterations only) and passes TestU01 tests. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro512** of the algorithm. - - See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear - PRNG, with low computation time, 64-bits output values and good randomness - characteristics. - See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Furthermore this class is callable: - rand = Xoroshiro512() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - currentS1 = self._state[1] - # advances the internal state of the PRNG - self._state[2] ^= self._state[0] - self._state[5] ^= self._state[1] - self._state[1] ^= self._state[2] - self._state[7] ^= self._state[3] - self._state[3] ^= self._state[4] - self._state[4] ^= self._state[5] - self._state[0] ^= self._state[6] - self._state[6] ^= self._state[7] - self._state[6] ^= (currentS1 << 11) & self._MODULO - self._state[7] = self._rotleft( self._state[7], 21 ) - # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(8) ] - - -#===== end of module xoroshiro512.py =================================== - diff --git a/PyRandLib/LICENSE b/PyRandLib/LICENSE deleted file mode 100644 index 1e480cb..0000000 --- a/PyRandLib/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/PyRandLib/README.md b/PyRandLib/README.md deleted file mode 100644 index 5402b3a..0000000 --- a/PyRandLib/README.md +++ /dev/null @@ -1,686 +0,0 @@ -# PyRandLib [![Latest release](http://img.shields.io/github/release/schmouk/pyrandlib.svg?style=plastic&labelColor=blueviolet&color=success)](https://github.com/schmouk/pyrandlib/releases) -Many best in class pseudo random generators grouped into one simple library. - - - -## License -PyRandLib is distributed under the MIT license for its largest use. -If you decide to use this library, please add the copyright notice to your -software as stated in the LICENSE file. - -``` -Copyright (c) 2016-2022 Philippe Schmouker, - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - - - - -## Intro -This library implements some of the best-in-class pseudo random generators -as evaluated by Pierre L'Ecuyer and Richard Simard in their famous paper -"TestU01: A C library for empirical testing of random number generators" -(ACM Trans. Math. Softw. Vol. 33 N.4, August 2007 - see reference [1]. The -reader will take benefit reading L'Ecuyer & Simard's paper. - -Each of the Pseudo Random Generator (PRG) implemented in PyRandLib is self -documented. Names of classes directly refer to the kind of PRG they implem- -ent augmented with some number characterizing their periodicity. All of -their randomness characteristics are explained in every related module. - - -### Why not Mersenne twister? - -The Mersenne twister PRG proposed by Matsumoto and Nishimura - see [5] - is -the most widely used PRG. The Random class of module random in Python -implements this PRG. It is also implemented in C++ and Java standard -libraries for instance. - -It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, -this PRG is a little bit long to compute (up to 3 times than LCGs, 60% more -than LFibs and a little bit less than MRGs, see below at section 'Architect- -ure overview'). Moreover, it fails 4 of the hardest TestU01 tests. You can -still use it as your preferred PRG but PyRandLib implements many other PRGs -which are either far faster or far better in terms of generated pseudo- -randomness than the Mersenne twister PRG. - - - -## Installation -Currently, the only way to install PyRandLib is to download the .zip or -.tar.gz archive, then to directly put sub-directory 'PyRandLib' from archive -into directory 'site-packages', in the main directory 'Lib' of your Python -environment. See https://schmouk.github.io/PyRandLib/ for an easy access to -download versions or click on tab **releases** on home page of GitHub -repository. - -A distribution version (to be installed via pip or easy-install in cmd tool -or in console) is to come. - - - -## Randomness evaluation -In [1], every known PRG at the time of the editing has been tested according -to three different sets of tests: -* _small crush_ is a small set of simple tests that quickly tests some of -the expected characteristics for a pretty good PRG; -* _crush_ is a bigger set of tests that test more deeply expected random -characteristics; -* _big crush_ is the ultimate set of difficult tests that any GOOD PRG -should definitively pass. - -We give you here below a copy of the resulting table for the PRGs that have -been implemented in PyRandLib plus the Mersenne twister one which is not -implemented in PyRabdLib, as provided in [1]. - - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19937 | 4.30 | 1.6 | 0 | 2 | 2 | - - - -## Implementation -Current implementation of PyRandLib uses Python 3.x with no Cython version. -It has been tested with Python 3.8 but should run with all of Python 3. - -Note 1: PyRandLib version 1.1 and below should work with all versions of -Python 3. In version 1.2, we have added underscores in numerical constants -for the better readability of the code. This feature has been introduced in -Python 3.6. If you want to use PyRandLib version 1.2 or above with Python -3.5 or below, removing these underscores should be sufficient to have the -library running correctly. - -Note 2: no version or PyRandLib will ever be provided for Python 2 which is -a no more maintained version of the Python language. - -Note 3: a Cython version of PyRandLib might be delivered in a next release. -Up today, no date is planned for this. - - -## New in release 1.2 -This is available starting at version 1.2 of PyRandLib. The call operator -(i.e., '()') gets a new signature which is still backward compatible with -previous versions of this library. Its new use is described here below. The -implementation code can be found in class `BaseRandom`, in module -`baserandom.py`. - - from fastrand63 import FastRand63 - - rand = FastRand63() - - # prints a float random value ranging in [0.0, 1.0] - print( rand() ) - - # prints an integer random value ranging in [0, 5] - print( rand(5) ) - - # prints a float random value ranging in [0.0, 20.0] - print( rand(20.0) - - # prints a list of 10 integer values each ranging in [0, 5] - print( rand(5, 10) ) - - # prints a list of 10 float values each ranging in [0.0, 1.0] - print( rand(times=10) ) - - # prints a list of 4 random values ranging respectively in - # [0, 5], [0.0, 50.0], [0.0, 500.0] and [0, 5000] - print( rand(5, 50.0, 500.0, 5000) ) - - # a more complex call which prints something like: - # [ [3, 11.64307079016269, 127.65395855782158, 4206, [2, 0, 1, 4, 4, 1, 2, 0]], - # [2, 34.22526698212995, 242.54183578253426, 2204, [5, 3, 5, 4, 2, 0, 1, 3]], - # [0, 17.77303802057933, 417.70662295909983, 559, [4, 1, 5, 0, 5, 3, 0, 5]] ] - print( rand( (5, 50.0, 500.0, 5000, [5]*8), times=3 ) ) - - - -## Architecture overview -Each of the implemented PRG is described in an independent module. The name -of the module is directly related to the name of the related class. - - -### BaseRandom - the base class for all PRGs - -**BaseRandom** is the base class for every implemented PRG in library -**PyRandLib**. It inherits from the Python built-in class random.Random. It -aims at providing simple common behavior for all PRG classes of the library, -the most noticeable one being the 'callable' nature of every implemented -PRGs. For instance: - - rand = BaseRandom() - print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a uniform pseudo-random value within [a, b) - -Inheriting from the Python built-in class random.Random, **BaseRandom** -provides access to many useful distribution functions as described in -later section **Inherited Distribution Functions**. - -Furthermore, every inheriting class may override methods: - -* random(), -* seed(), -* getrandbits(k), -* getstate() and -* setstate(). - -This lets inheriting classes implement the PRGs related core methods. - - - -### FastRand32 - 2^32 periodicity - -**FastRand32** implements a Linear Congruential Generator dedicated to -32-bits calculations with very short period (about 4.3e+09) but very short -time computation. - -LCG models evaluate pseudo-random numbers suites *x(i)* as a simple -mathematical function of *x(i-1)*: - - x(i) = ( a * x(i-1) + c ) mod m - -The implementation of **FastRand32** is based on (*a*=69069, *c*=1) since -these two values have evaluated to be the 'best' ones for LCGs within -TestU01 while m = 2^32. - -Results are nevertheless considered to be poor as stated in the evaluation -done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not -recommended to use such pseudo-random numbers generators for serious -simulation applications. - -See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with low -computation time and 'better' randomness characteristics. - - - -### FastRand63 - 2^63 periodicity - -**FastRand63** implements a Linear Congruential Generator dedicated to -63-bits calculations with a short period (about 9.2e+18) and very short -time computation. - -LCG models evaluate pseudo-random numbers suites *x(i)* as a simple -mathematical function of *x(i-1)*: - - x(i) = ( a * x(i-1) + c ) mod m - -The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) -since these two values have evaluated to be the 'best' ones for LCGs within -TestU01 while *m* = 2^63. - -Results are nevertheless considered to be poor as stated in the evaluation -done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not -recommended to use such pseudo-random numbers generators for serious -simulation applications, even if FastRandom63 fails on very far less tests -than does FastRandom32. - -See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% -lower computation time. - - - -### MRGRand287 - 2^287 periodicity - -**MRGRand287** implements a fast 32-bits Multiple Recursive Generator (MRG) -with a long period (2^287, i.e. 2.49e+86) and low computation time (about -twice the computation time of above LCGs) but 256 integers memory -consumption. - -Multiple Recursive Generators (MRGs) use recurrence to evaluate -pseudo-random numbers suites. For 2 to more different values of *k*, -recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - -MRGs offer very large periods with the best known results in the evaluation -of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard. It -is therefore strongly recommended to use such pseudo-random numbers -generators rather than LCG ones for serious simulation applications. - -The implementation of this MRG 32-bits model is finally based on a Lagged -Fibonacci generator (LFIB), the Marsa-LFIB4 one. - -Lagged Fibonacci generators *LFib( m, r, k, op)* use the recurrence - - x(i) = ( x(i-r) op (x(i-k) ) mod m - -where op is an operation that can be - + (addition), - - (substraction), - * (multiplication), - ^(bitwise exclusive-or). - -With the + or - operation, such generators are true MRGs. They offer very -large periods with the best known results in the evaluation of their -randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in their -paper. - -The Marsa-LIBF4 version, i.e. **MRGRand287** implementation, uses the -recurrence: - - x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 - - - -### MRGRand1457 - 2^1457 periodicity - -**MRGRand1457** implements a fast 31-bits Multiple Recursive Generator with -a longer period than MRGRan287 (2^1457 vs. 2^287, i.e. 4.0e+438 vs. 2.5e+86) -and 80 % more computation time but with much less memory space consumption -(47 vs. 256 integers). - -The implementation of this MRG 31-bits model is based on DX-47-3 -pseudo-random generator proposed by Deng and Lin, see [2]. The DX-47-3 -version uses the recurrence: - - x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) - - - -### MRGRand49507 - 2^49507 periodicity - -**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with -the longer period of all of the PRGs that are implemented if **PyRandLib** -(2^49507, i.e. 1.2e+14903) with low computation time also (same as for -MRGRand287) but use of much more memory space (1597 integers). - -The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' -MRG proposed by Deng, see [3]. It uses the recurrence: - - x(i) = (-2^25-2^7) * ( x(i-7) + x(i-1597) ) mod (2^31-1) - - - -### LFibRand78 - 2^78 periodicity - -**LFibRand78** implements a fast 64-bits Lagged Fibonacci generator (LFib). -Lagged Fibonacci generators *LFib( m, r, k, op)* use the recurrence - - x(i) = ( x(i-r) op (x(i-k) ) mod m - -where op is an operation that can be - + (addition), - - (substraction), - * (multiplication), - ^(bitwise exclusive-or). - -With the + or - operation, such generators are MRGs. They offer very large -periods with the best known results in the evaluation of their randomness, -as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard -while offering very low computation times. - -The implementation of **LFibRand78** is based on a Lagged Fibonacci -generator (LFib) which uses the recurrence: - - x(i) = ( x(i-5) + x(i-17) ) mod 2^64 - -It offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time -due to the use of a 2^64 modulo (less than twice the computation time of -LCGs) and low memory consumption (17 integers). - -Please notice that the TestUO1 article states that the operator should be -'*' while George Marsaglia in its original article [4] used the operator -'+'. We've implemented in **PyRandLib** the original operator '+'. - - - -### LFibRand116 - 2^116 periodicity - -**LFibRand116** implements an LFib 64-bits generator proposed by George -Marsaglia in [4]. This PRG uses the recurrence - - x(i) = ( x(i-24) + x(i-55) ) mod 2^64 - -It offers a period of about 2^116 - i.e. 8.3e+34 - with low computation -time due to the use of a 2^64 modulo (less than twice the computation time -of LCGs) and some memory consumption (55 integers). - -Please notice that the TestUO1 article states that the operator should be -'*' while George Marsaglia in its original article [4] used the operator -'+'. We've implemented in **PyRandLib** the original operator '+'. - - - -### LFibRand668 - 2^668 periodicity - -**LFibRand668** implements an LFib 64-bits generator proposed by George -Marsaglia in [4]. This PRG uses the recurrence - - x(i) = ( x(i-273) + x(i-607) ) mod 2^64 - -It offers a period of about 2^668 - i.e. 1.2e+201 - with low computation -time due to the use of a 2^64 modulo (less than twice the computation time -of LCGs) and much memory consumption (607 integers). - -Please notice that the TestUO1 article states that the operator should be -'*' while George Marsaglia in its original article [4] used the operator -'+'. We've implemented in **PyRandLib** the original operator '+'. - - - -### LFibRand1340 - 2^1340 periodicity - -**LFibRand1340** implements an LFib 64-bits generator proposed by George -Marsaglia in [4]. This PRG uses the recurrence - - x(i) = ( x(i-861) + x(i-1279) ) mod 2^64 - -It offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation -time due to the use of a 2^64 modulo (less than twice the computation time -of LCGs) and much more memory consumption (1279 integers). - -Please notice that the TestUO1 article states that the operator should be -'*' while George Marsaglia in its original article [4] used the operator -'+'. We've implemented in **PyRandLib** the original operator '+'. - - - - -## Inherited Distribution and Generic Functions -(some of next explanation may be free to exact copy of Python 3.6 documentation. -See https://docs.python.org/3.6/library/random.html?highlight=random#module-random) - -Since the base class **BaseRandom** inherits from the built-in class -random.Random, every PRG class of **PyRandLib** gets automatic access to -the next distribution and generic methods: - - -**betavariate**(self, alpha, beta) - -Beta distribution. - -Conditions on the parameters are alpha > 0 and beta > 0. -Returned values range between 0 and 1. - - -**choice**(self, seq) - -Chooses a random element from a non-empty sequence. 'seq' has to be non -empty. - - -**choices**(population, weights=None, *, cum_weights=None, k=1) - -Returns a k sized list of elements chosen from the population with -replacement. If the population is empty, raises IndexError. - -If a weights sequence is specified, selections are made according to the -relative weights. Alternatively, if a cum_weights sequence is given, the -selections are made according to the cumulative weights (perhaps computed -using itertools.accumulate()). For example, the relative weights -[10, 5, 30, 5] are equivalent to the cumulative weights [10, 15, 45, 50]. -Internally, the relative weights are converted to cumulative weights before -making selections, so supplying the cumulative weights saves work. - -If neither weights nor cum_weights are specified, selections are made with -equal probability. If a weights sequence is supplied, it must be the same -length as the population sequence. It is a TypeError to specify both -weights and cum_weights. - -The weights or cum_weights can use any numeric type that interoperates with -the float values returned by random() (that includes integers, floats, and -fractions but excludes decimals). - -Notice: 'choices' has been provided since Python 3.6. It should be -implemented for older versions. - - -**expovariate**(self, lambd) - -Exponential distribution. - -lambd is 1.0 divided by the desired mean. It should be nonzero. (The -parameter should be called "lambda", but this is a reserved word in -Python.) Returned values range from 0 to positive infinity if lambd is -positive, and from negative infinity to 0 if lambd is negative. - - -**gammavariate**(self, alpha, beta) - -Gamma distribution. Not the gamma function! - -Conditions on the parameters are alpha > 0 and beta > 0. - - -**gauss**(self, mu, sigma) - -Gaussian distribution. - -mu is the mean, and sigma is the standard deviation. -This is slightly faster than the normalvariate() function. - -Not thread-safe without a lock around calls. - - -**getrandbits(self, k)** - -Returns a Python integer with k random bits. Inheriting generators may also -provide it as an optional part of their API. When available, getrandbits() -enables randrange() to handle arbitrarily large ranges. - - -**getstate**(self) - -Returns internal state; can be passed to setstate() later. - - -**lognormvariate**(self, mu, sigma) - -Log normal distribution. - -If you take the natural logarithm of this distribution, you'll get a normal -distribution with mean mu and standard deviation sigma. -mu can have any value, and sigma must be greater than zero. - - -**normalvariate**(self, mu, sigma) - -Normal distribution. - -mu is the mean, and sigma is the standard deviation. See method gauss() for -a faster but not thread-safe equivalent. - - -**paretovariate**(self, alpha) - -Pareto distribution. alpha is the shape parameter. - - -**randint**(self, a, b) - -Returns a random integer in range [a, b], including both end points. - - -**randrange**(self, stop) - -**randrange**(self, start, stop=None, step=1) - -Returns a randomly selected element from range(start, stop, step). This is -equivalent to choice( range(start, stop, step) ) without building a range -object. - -The positional argument pattern matches that of range(). Keyword arguments -should not be used because the function may use them in unexpected ways. - - -**sample**(self, population, k) - -Chooses k unique random elements from a population sequence or set. - -Returns a new list containing elements from the population while leaving -the original population unchanged. The resulting list is in selection -order so that all sub-slices will also be valid random samples. This allows -raffle winners (the sample) to be partitioned into grand prize and second -place winners (the subslices). - -Members of the population need not be hashable or unique. If the population -contains repeats, then each occurrence is a possible selection in the -sample. - -To choose a sample in a range of integers, use range as an argument. This -is especially fast and space efficient for sampling from a large -population: sample(range(10000000), 60) - - -**seed**(self, a=None, version=2) - -Initialize internal state from hashable object. - -None or no argument seeds from current time or from an operating system -specific randomness source if available. - -For version 2 (the default), all of the bits are used if *a* is a str, -bytes, or bytearray. For version 1, the hash() of *a* is used instead. - -If *a* is an int, all bits are used. - - -**setstate**(self, state) - -Restores internal state from object returned by getstate(). - - -**shuffle**(self, x, random=None) - -Shuffle the sequence x in place. Returns None. - -The optional argument random is a 0-argument function returning a random -float in [0.0, 1.0); by default, this is the function random(). - -To shuffle an immutable sequence and return a new shuffled list, use -sample(x, k=len(x)) instead. - -Note that even for small len(x), the total number of permutations of x can -quickly grow larger than the period of most random number generators. This -implies that most permutations of a long sequence can never be generated. -For example, a sequence of length 2080 is the largest that can fit within -the period of the Mersenne Twister random number generator. - - -**triangular**(self, low=0.0, high=1.0, mode=None) - -Triangular distribution. - -Continuous distribution bounded by given lower and upper limits, and having -a given mode value in-between. Returns a random floating point number *N* -such that low <= *N* <= high and with the specified mode between those -bounds. The low and high bounds default to zero and one. The mode argument -defaults to the midpoint between the bounds, giving a symmetric -distribution. - -http://en.wikipedia.org/wiki/Triangular_distribution - - -**uniform**(self, a, b) - -Gets a random number in the range [a, b) or [a, b] depending on rounding. - - -**vonmisesvariate**(self, mu, kappa) - -Circular data distribution. - -mu is the mean angle, expressed in radians between 0 and 2*pi, and kappa is -the concentration parameter, which must be greater than or equal to zero. -If kappa is equal to zero, this distribution reduces to a uniform random -angle over the range 0 to 2*pi. - - -**weibullvariate**(self, alpha, beta) - -Weibull distribution. - -alpha is the scale parameter and beta is the shape parameter. - - - -## References - -**[1]** Pierre L'Ecuyer and Richard Simard. 2007. -*TestU01: A C library for empirical testing of random number generators*. -ACM Transaction on Mathematical Software, Vol.33 N.4, Article 22 (August 2007), 40 pages. DOI: http://dx.doi.org/10.1145/1268776.1268777 - -BibTex: -@article{L'Ecuyer:2007:TCL:1268776.1268777, - author = {L'Ecuyer, Pierre and Simard, Richard}, - title = {TestU01: A C Library for Empirical Testing of Random Number Generators}, - journal = {ACM Trans. Math. Softw.}, - issue_date = {August 2007}, - volume = {33}, - number = {4}, - month = aug, - year = {2007}, - issn = {0098-3500}, - pages = {22:1--22:40}, - articleno = {22}, - numpages = {40}, - url = {http://doi.acm.org/10.1145/1268776.1268777}, - doi = {10.1145/1268776.1268777}, - acmid = {1268777}, - publisher = {ACM}, - address = {New York, NY, USA}, - keywords = {Statistical software, random number generators, random number tests, statistical test}, -} - - -**[2]** Lih-Yuan Deng & Dennis K. J. Lin. 2000. -*Random number generation for the new century*. -The American Statistician Vol.54, N.2, pp. 145–150. - -BibTex: -@article{doi:10.1080/00031305.2000.10474528, -author = { Lih-Yuan Deng and Dennis K. J. Lin }, -title = {Random Number Generation for the New Century}, -journal = {The American Statistician}, -volume = {54}, -number = {2}, -pages = {145-150}, -year = {2000}, -doi = {10.1080/00031305.2000.10474528}, -URL = {ttp://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, -eprint = {http://amstat.tandfonline.com/doi/pdf/10.1080/00031305.2000.10474528} -} - - -**[3]** Lih-Yuan Deng. 2005. -*Efficient and portable multiple recursive generators of large order*. -ACM Transactions on Modeling and Computer. Simulation 15:1. - - -**[4]** Georges Marsaglia. 1985. -*A current view of random number generators*. -In Computer Science and Statistics, Sixteenth Symposium on the Interface. Elsevier Science Publishers, North-Holland, -Amsterdam, 1985, The Netherlands. pp. 3–10. - - -**[5]** Makoto Matsumoto and Takuji Nishimura. 1998. -*Mersenne twister: A 623-dimensionally equidistributed uniform pseudo-random number generator.* -In ACM Transactions on Modeling and Computer Simulation (TOMACS) - Special issue on uniform random number generation. -Vol.8 N.1, Jan. 1998, pp. 3-30. diff --git a/PyRandLib/__init__.py b/PyRandLib/__init__.py deleted file mode 100644 index bd75909..0000000 --- a/PyRandLib/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -This file is part of library PyRandLib. -It is provided under MIT License. -Please see files README.md and LICENSE. - -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com -""" - -from .basecwg import BaseCWG -from .baselcg import BaseLCG -from .baselfib64 import BaseLFib64 -from .basemelg import BaseMELG -from .basemrg import BaseMRG -from .baserandom import BaseRandom -from .basesquares import BaseSquares -from .basewell import BaseWELL -from .basexoroshiro import BaseXoroshiro -from .cwg64 import Cwg64 -from .cwg128_64 import Cwg128_64 -from .cwg128 import Cwg128 -from .fastrand32 import FastRand32 -from .fastrand63 import FastRand63 -from .lfib78 import LFib78 -from .lfib116 import LFib116 -from .lfib668 import LFib668 -from .lfib1340 import LFib1340 -from .melg607 import Melg607 -from .melg19937 import Melg19937 -from .melg44497 import Melg44497 -from .mrg287 import Mrg287 -from .mrg1457 import Mrg1457 -from .mrg49507 import Mrg49507 -from .pcg64_32 import Pcg64_32 -from .pcg128_64 import Pcg128_64 -from .pcg1024_32 import Pcg1024_32 -from .squares32 import Squares32 -from .squares64 import Squares64 -from .well512a import Well512a -from .well1024a import Well1024a -from .well19937c import Well19937c -from .well44497b import Well44497b -from .xoroshiro256 import Xoroshiro256 -from .xoroshiro512 import Xoroshiro512 -from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib/annotation_types.py b/PyRandLib/annotation_types.py deleted file mode 100644 index e097004..0000000 --- a/PyRandLib/annotation_types.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Tuple, Union - -Numerical = Union[ int, float ] -StatesList = Union[ Tuple[int], List[int] ] -StatesListAndExt = Tuple[ StatesList, int ] -StateType = Union[ StatesList, StatesListAndExt ] -SeedStateType = Union[ Numerical, StateType ] - - -#===== end of PyRandLib.annotation_types =============================== - -# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py deleted file mode 100644 index 40c2aa5..0000000 --- a/PyRandLib/basecwg.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesListAndExt - - -#============================================================================= -class BaseCWG( BaseRandom ): - """Definition of the base class for all Collatz-Weyl pseudo-random Generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - CWG models are chaotic generators that are combined with Weyl sequences to - eliminate the risk of short cycles. They have a large period, a uniform - distribution, and the ability to generate multiple independent streams by - changing their internal parameters (Weyl increment). CWGs owe their - exceptional quality to the arithmetical dynamics of noninvertible, - generalized, Collatz mappings based on the wellknown Collatz conjecture. - There is no jump function, but each odd number of the Weyl increment - initiates a new unique period, which enables quick initialization of - independent streams (this text is extracted from [8], see README.md). - - The internal implementation of the CWG algorithm varies according to its - implemented version. See implementation classes to get their formal - description. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with low computation time, medium period, 64- bits output values and very - good randomness characteristics. - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> StatesListAndExt: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For CWG, this state is defined by a list of control values - (a, weyl and s - or a list of 4 coeffs) and an internal state - value, which are used in methods 'next() and 'setstate() of - every inheriting class. - - All inheriting classes MUST IMPLEMENT this method. - """ - return (self._a, self._weyl, self._s, self._state) - - -#===== end of module basecwg.py ======================================== diff --git a/PyRandLib/baselcg.py b/PyRandLib/baselcg.py deleted file mode 100644 index acd6ce2..0000000 --- a/PyRandLib/baselcg.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical - - -#============================================================================= -class BaseLCG( BaseRandom ): - """Definition of the base class for all LCG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the evaluation - done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in - 'TestU01: A C Library for Empirical Testing of Random Number Generators - - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August - 2007'. It is not recommended to use such pseudo-random numbers generators - for serious simulation applications. - - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low - computation time but shorter period and worse randomness characteristics - than for FastRand63. - See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with - low computation time also, longer period and quite better randomness - characteristics than for FastRand32. - - Furthermore this class is callable: - rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> int: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For LCG, the state is defined with a single integer, 'self._state', - which has to be used in methods 'next() and 'setstate() of every - inheriting class. - """ - return self._state - -#===== end of module baselcg.py ======================================== diff --git a/PyRandLib/baselfib64.py b/PyRandLib/baselfib64.py deleted file mode 100644 index 96da67d..0000000 --- a/PyRandLib/baselfib64.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseLFib64( BaseRandom ): - """The base class for all LFib PRNG based on 64-bits numbers. - - Definition of the base class for all LFib pseudo-random generators based - on 64-bits generated numbers. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^(bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. - 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and - 2.4e+403 periods) while same computation time and far higher precision (64-bits - calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 - integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an - example. - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an - index in this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module baselfib64.py ===================================== - diff --git a/PyRandLib/basemelg.py b/PyRandLib/basemelg.py deleted file mode 100644 index e69665b..0000000 --- a/PyRandLib/basemelg.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseMELG( BaseRandom ): - """Definition of the base class for all MELG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: while the WELL algorithm use 32-bits integers as their internal state and - output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. This is the shortest period version proposed in paper [11]. - See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for - an example. - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE 64-bits integers, an index in this list and an - additional 64-bits integer as a state extension. Should _seedState - be a sole integer or float then it is used as initial seed for the - random filling of the internal state of the PRNG. Should _seedState - be anything else (e.g. None) then the shuffling of the local - current time value is used as such an initial seed. - - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE 64-bits integers, an index - in this list and an additional 64-bits integer as a state extension. - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - Should _seedstate not contain a list of self._STATE_SIZE 64- - bits integers, a value for attribute self._index and a value - for attribute self._extState, this method tries its best to - initialize all these values. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - elif count == 2: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: StateType = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module basemelg.py ======================================= diff --git a/PyRandLib/basemrg.py b/PyRandLib/basemrg.py deleted file mode 100644 index 8059c8e..0000000 --- a/PyRandLib/basemrg.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical, SeedStateType, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseMRG( BaseRandom ): - """Definition of the base class for all MRG pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (32-bits modulus) but use of more memory space (1597 - integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. - See Mrg287 for an example. - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for the - random filling of the internal list of self._STATE_SIZE integers. - Should _seedState be anything else (e.g. None) then the shuffling - of the local current time value is used as such an initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in - this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] - - -#===== end of module basemrg.py ======================================== diff --git a/PyRandLib/basepcg.py b/PyRandLib/basepcg.py deleted file mode 100644 index 8c9a423..0000000 --- a/PyRandLib/basepcg.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import Numerical - - -#============================================================================= -class BasePCG( BaseRandom ): - """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low - computation time and medium period, with 2 32-bits word integers memory - consumption. Output values are returned on 32 bits. - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> int: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - For LCG, the state is defined with a single integer, 'self._value', - which has to be used in methods 'random() and 'setstate() of every - inheriting class. - """ - return self._state - -#===== end of module basepcg.py ======================================== diff --git a/PyRandLib/baserandom.py b/PyRandLib/baserandom.py deleted file mode 100644 index 12b8598..0000000 --- a/PyRandLib/baserandom.py +++ /dev/null @@ -1,401 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from random import Random -from typing import List, Tuple, Union - -from .annotation_types import Numerical, SeedStateType, StateType - - -#============================================================================= -class BaseRandom( Random ): - """This is the base class for all pseudo-random numbers generators. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a - 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with - very low memory consumption (resp. 1 and 2 32-bits integers). - - See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib - generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, - 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher - precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and - 1279 32-bits integers). - - See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 32-bits integers memory consumption. - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (32-bits 47 integers). - See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low - computation time too (31-bits modulus) but use of more memory space (1597 - 32-bits integers). - - See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low - computation time, and for very low memory consumption for the two first (resp. 4, - 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, - i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump - ahead features. Since they all are exposing only a part of their internal state, - they are difficult to reverse and to predict. - - See Well512a, Well1024a, Well19937c and Well44479b for large to very large period - generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, - 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation - times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, - 624 and 1391 32-bits integers. - - Python built-in class random.Random is subclassed here to use a different basic - generator of our own devising: in that case, overriden methods are: - - random(), seed(), getstate(), and setstate(). - - Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method - next(). - - Furthermore this class and all its inheriting sub-classes are callable. Example: - rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Please notice that for simulating the roll of a dice you should program: - diceRoll = UFastRandom() - print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. - Such a programming is a simplified while still robust emulation of inherited - methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). - - Inheriting from random.Random, next methods are also available: - | - | betavariate(self, alpha, beta) - | Beta distribution. - | - | Conditions on the parameters are alpha > 0 and beta > 0. - | Returned values range between 0 and 1. - | - | - | choice(self, seq) - | Choose a random element from a non-empty sequence. - | - | - | expovariate(self, lambd) - | Exponential distribution. - | - | lambd is 1.0 divided by the desired mean. It should be - | nonzero. (The parameter would be called "lambda", but that is - | a reserved word in Python.) Returned values range from 0 to - | positive infinity if lambd is positive, and from negative - | infinity to 0 if lambd is negative. - | - | - | gammavariate(self, alpha, beta) - | Gamma distribution. Not the gamma function! - | - | Conditions on the parameters are alpha > 0 and beta > 0. - | - | - | gauss(self, mu, sigma) - | Gaussian distribution. - | - | mu is the mean, and sigma is the standard deviation. This is - | slightly faster than the normalvariate() function. - | - | Not thread-safe without a lock around calls. - | - | - | getrandbits(self, k) - | Returns a non-negative Python integer with k random bits. - | Changed since version 3.9: This method now accepts zero for k. - | - | - | getstate(self) - | Return internal state; can be passed to setstate() later. - | - | - | lognormvariate(self, mu, sigma) - | Log normal distribution. - | - | If you take the natural logarithm of this distribution, you'll get a - | normal distribution with mean mu and standard deviation sigma. - | mu can have any value, and sigma must be greater than zero. - | - | - | normalvariate(self, mu, sigma) - | Normal distribution. - | - | mu is the mean, and sigma is the standard deviation. - | - | - | paretovariate(self, alpha) - | Pareto distribution. alpha is the shape parameter. - | - | - | randbytes(self, n) - | Generate n random bytes. - | This method should not be used for generating security tokens. - | Notice: this method has been added in Python 3.9. It is implemented - | in PyRandLib for former versions of the language also. - | - | - | randint(self, a, b) - | Return random integer in range [a, b], including both end points. - | - | - | randrange(self, start, stop=None, step=1, int=) - | Choose a random item from range(start, stop[, step]). - | - | This fixes the problem with randint() which includes the - | endpoint; in Python this is usually not what you want. - | - | Do not supply the 'int' argument. - | - | - | sample(self, population, k) - | Chooses k unique random elements from a population sequence or set. - | - | Returns a new list containing elements from the population while - | leaving the original population unchanged. The resulting list is - | in selection order so that all sub-slices will also be valid random - | samples. This allows raffle winners (the sample) to be partitioned - | into grand prize and second place winners (the subslices). - | - | Members of the population need not be hashable or unique. If the - | population contains repeats, then each occurrence is a possible - | selection in the sample. - | - | To choose a sample in a range of integers, use range as an argument. - | This is especially fast and space efficient for sampling from a - | large population: sample(range(10000000), 60) - | - | - | seed(self, a=None, version=2) - | Initialize internal state from hashable object. - | - | None or no argument seeds from current time or from an operating - | system specific randomness source if available. - | - | For version 2 (the default), all of the bits are used if *a *is a str, - | bytes, or bytearray. For version 1, the hash() of *a* is used instead. - | - | If *a* is an int, all bits are used. - | - | - | setstate(self, state) - | Restore internal state from object returned by getstate(). - | - | - | shuffle(self, x, random=None, int=) - | x, random=random.random -> shuffle list x in place; return None. - | - | Optional arg random is a 0-argument function returning a random - | float in [0.0, 1.0); by default, the standard random.random. - | - | - | triangular(self, low=0.0, high=1.0, mode=None) - | Triangular distribution. - | - | Continuous distribution bounded by given lower and upper limits, - | and having a given mode value in-between. - | - | http://en.wikipedia.org/wiki/Triangular_distribution - | - | - | uniform(self, a, b) - | Get a random number in the range [a, b) or [a, b] depending on rounding. - | - | - | vonmisesvariate(self, mu, kappa) - | Circular data distribution. - | - | mu is the mean angle, expressed in radians between 0 and 2*pi, and - | kappa is the concentration parameter, which must be greater than or - | equal to zero. If kappa is equal to zero, this distribution reduces - | to a uniform random angle over the range 0 to 2*pi. - | - | - | weibullvariate(self, alpha, beta) - | Weibull distribution. - | - | alpha is the scale parameter and beta is the shape parameter. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 32 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seed: SeedStateType = None) -> None: - """Constructor. - - Should _seed be None or not a number then the local time is used - (with its shuffled value) as a seed. - - Notice: the Python built-in base class random.Random internally - calls method setstate() which MUST be overridden in classes that - inherit from class BaseRandom. - """ - super().__init__( _seed ) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - - This is the core of the PRNGs. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def random(self) -> float: - """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). - - Inheriting classes MUST OVERRIDE the value of the base class constant - attribute _NORMALIZE when the random integer values they return are - coded on anything else than 32 bits. - """ - return self.next() * self._NORMALIZE - - - #------------------------------------------------------------------------- - def getrandbits(self, k: int) -> int: - """Returns k bits from the internal state of the generator. - - k must be a positive value greater or equal to zero. - """ - assert k >= 0, "the returned bits count must not be negative" - assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" - - return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) - - - #------------------------------------------------------------------------- - def randbytes(self, n: int) -> bytes: - """Generates n random bytes. - - This method should not be used for generating security tokens. - (use Python built-in secrets.token_bytes() instead) - """ - assert n >= 0 # and self._OUT_BITS >= 8 - return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can then be passed to setstate() to restore the state. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def setstate(self, _state: StateType) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call to getstate(), - and setstate() restores the internal state of the generator to what - it was at the time setstate() was called. - Inheriting classes MUST IMPLEMENT this method. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------- - def seed(self, _seed: SeedStateType = None) -> None: - """Initiates the internal state of this pseudo-random generator. - """ - try: - self.setstate( _seed ) - except: - super().seed( _seed ) - - - #------------------------------------------------------------------------- - def __call__(self, _max : Union[Numerical, - Tuple[Numerical], - List[Numerical]] = 1.0, - times: int = 1 ) -> Union[Numerical, List[Numerical]]: - """This class's instances are callable. - - The returned value is uniformly contained within the - interval [0.0 : _max]. When times is set, a list of - iterated pseudo-random values is returned. 'times' - must be an integer. If less than 1 it is forced to - be 1. - '_max' may be a list or a tuple of values, in which - case a list of related pseudo-random values is - returned with entries of the same type than the same - indexed entry in '_max'. - """ - assert isinstance( times, int ) - if times < 1: - times = 1 - - if isinstance( _max, int ): - ret = [ int(_max * self.random()) for _ in range(times) ] - elif isinstance( _max, float ): - ret = [ _max * self.random() for _ in range(times) ] - else: - try: - if times == 1: - ret = [ self(m,1) for m in _max ] - else: - ret = [ [self(m,1) for m in _max] for _ in range(times) ] - except: - ret = [ self.__call__(times=1) ] - - return ret[0] if len(ret) == 1 else ret - - - #------------------------------------------------------------------------- - @classmethod - def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: - """Returns the value of a left rotating by _rotCount bits - - Useful for some inheriting classes. - """ - #assert 1 <=_rotCount <= _bitsCount - loMask = (1 << (_bitsCount - _rotCount)) - 1 - hiMask = ((1 << _bitsCount) - 1) ^ loMask - hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) - return ((_value & loMask) << _rotCount) | hiBits - - -#===== end of module baserandom.py ===================================== diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py deleted file mode 100644 index 2968237..0000000 --- a/PyRandLib/basesquares.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesList -from .splitmix import SplitMix32 - - -#============================================================================= -class BaseSquares( BaseRandom ): - """Definition of the base class for the Squares counter-based pseudo-random Generator. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Squares models are based on an incremented counter and a key. The - algorithm squares a combination of the counter and the key values, - and exchanges the upper and lower bits of the combination, the - whole repeated a number of times (4 to 5 rounds). Output values - are provided on 32-bits or on 64-bits according to the model. See - [9] in README.md. - - See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 32-bits output values and - very good randomness characteristics. - - See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 64-bits output values and - very good randomness characteristics. Caution: the 64-bits version - should not pass the birthday test, which is a randmoness issue, - while this is not mentionned in the original paper (see [9] in - file README.md). - - Furthermore this class is callable: - rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def getstate(self) -> StatesList: - """Returns an object capturing the current internal state of the generator. - """ - return (self._counter, self._key) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType) -> None: - """Restores or sets the internal state of the generator. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._counter = 0 - self._key = self._initKey( _state ) - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - self._counter = 0 - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) - else: - self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) - - else: - try: - self._counter = _state[0] & 0xffff_ffff_ffff_ffff - self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd - except: - # uses local time as initial seed - self._counter = 0 - self._key = self._initKey() - - - #------------------------------------------------------------------------- - def _initKey(self, _seed: int = None) -> int: - """Initalizes the attribute _key according to the original recommendations - see [9]. - """ - hexDigits = [ i for i in range(1, 16) ] - key = 0 - - initRand = SplitMix32( _seed ) - - # 8 high hexa digits - all different - n = 15 - while n >= 8: - k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class - h = hexDigits[ k ] - key <<= 4 - key += h - n -= 1 - if k < n: - hexDigits[ k ] = hexDigits[ n ] - hexDigits[ n ] = h - - # 8 low hexa digits - all different - n = 15 - while n >= 8: - k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class - h = hexDigits[ k ] - key <<= 4 - key += h - n -= 1 - if k < n: - hexDigits[ k ] = hexDigits[ n ] - hexDigits[ n ] = h - - return key | 1 # Notice: key must be odd - - -#===== end of module basesquares.py ==================================== diff --git a/PyRandLib/basewell.py b/PyRandLib/basewell.py deleted file mode 100644 index b7dc2ac..0000000 --- a/PyRandLib/basewell.py +++ /dev/null @@ -1,307 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StateType -from .splitmix import SplitMix32 - - -#============================================================================= -class BaseWELL( BaseRandom ): - """Definition of the base class for all WELL pseudo-random generators. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in the 4 different versions implemented here has been coded - here as a direct implementation of their descriptions in the initial paper - "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François - PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO - (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, - No. 1, March 2006, Pages 1–16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - So, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory little consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for - an example. - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937b generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for the - random filling of the internal list of self._STATE_SIZE integers. - Should _seedState be anything else (e.g. None) then the shuffling - of the local current time value is used as such an initial seed. - - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in - this list (index value being then in range(0,self._STATE_SIZE). - """ - return (self._state[:], self._index) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: StateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) % self._STATE_SIZE - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: StateType = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed and the value+1 of the modulo. - initRand = SplitMix32( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - - #------------------------------------------------------------------------- - @classmethod - def _M0(cls, x: int = None) -> int: - return 0 - - #------------------------------------------------------------------------- - @classmethod - def _M1(cls, x: int) -> int: - return x - - #------------------------------------------------------------------------- - @classmethod - def _M2_pos(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x >> t - - #------------------------------------------------------------------------- - @classmethod - def _M2_neg(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return (x << t) & 0xffff_ffff - - #------------------------------------------------------------------------- - @classmethod - def _M3_pos(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x ^ (x >> t) - - #------------------------------------------------------------------------- - @classmethod - def _M3_neg(cls, x: int, t: int) -> int: - #assert 0 <= t < 32 - return x ^ ((x << t) & 0xffff_ffff) - - #------------------------------------------------------------------------- - @classmethod - def _M4(cls, x: int, a: int) -> int: - #assert 0 <= a <= 0xffff_ffff - return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 - - #------------------------------------------------------------------------- - @classmethod - def _M5_pos(cls, x: int, t: int, a: int) -> int: - #assert 0 <= t < 32 - #assert 0 <= b <= 0xffff_ffff - return x ^ ((x >> t) & a) - - #------------------------------------------------------------------------- - @classmethod - def _M5_neg(cls, x: int, t: int, a: int) -> int: - #assert 0 <= t < 32 - #assert 0 <= a <= 0xffff_ffff - return x ^ ((x << t) & a) - - #------------------------------------------------------------------------- - @classmethod - def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: - #assert 0 <= q < 32 - #assert 0 <= t < 32 - #assert 0 <= s < 32 - #assert 0 <= a <= 0xffff_ffff - y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) - return y ^ a if x & (1 << t) else y - - #------------------------------------------------------------------------- - @classmethod - def _d(cls, s: int) -> int: - #assert 0 <= s < 32 - return 0xffff_ffff ^ (1 << s) - - #------------------------------------------------------------------------- - @classmethod - def _tempering(cls, x: int, b: int, c: int) -> int: - #assert 0 <= b <= 0xffff_ffff - #assert 0 <= c <= 0xffff_ffff - #assert 0 <= w <= 32 - # notice: the generic algorithm truncs x on w-bits. All of the implemented - # ones in PyRandLib are set on 32-bits. So, no truncation takes place here - x = x ^ (((x << 7) & 0xffff_ffff) & b) - return x ^ (((x << 15) & 0xffff_ffff) & c) - - #------------------------------------------------------------------------- - @property - def _a1(self): - return 0xda44_2d24 - - @property - def _a2(self): - return 0xd3e4_3ffd - - @property - def _a3(self): - return 0x8bdc_b91e - - @property - def _a4(self): - return 0x86a9_d87e - - @property - def _a5(self): - return 0xa8c2_96d1 - - @property - def _a6(self): - return 0x5d6b_45cc - - @property - def _a7(self): - return 0xb729_fcec - -#===== end of module basewell.py ======================================= diff --git a/PyRandLib/basexoroshiro.py b/PyRandLib/basexoroshiro.py deleted file mode 100644 index 1226391..0000000 --- a/PyRandLib/basexoroshiro.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Union - -from .baserandom import BaseRandom -from .annotation_types import Numerical, StatesList, StateType -from .splitmix import SplitMix64 - - -#============================================================================= -class BaseXoroshiro( BaseRandom ): - """The base class for all xoroshiro PRNGs. - - Definitiion of the base class for all versions of the xoroshiro algorithm - implemented in PyRandLib. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number - Generators. The xoroshiro linear transformation updates cyclically two words of a - larger state array. The base xoroshiro linear transformation is obtained combining - a rotation, a shift, and again a rotation. - (extracted from the original paper, see [10] in file README.md) - - An addition or a multiplication operation is internally applied also to the state - of the PRNGs. Doubling the same operation has proven to enhance then randomness - quality of the PRNG. This is the model of the algorithms that is implemeted in - PyRandLib. - - The implemented algorithms shortly escape from the zeroland (10 to 100 calls are - enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 - version of the algorithm has nevertheless shown close repeats flaws, with a bad - Hamming weight near zero. Xoroshiro512 seems to best fit this property. - (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). - - See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. - 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 - periods), 64-bits precision calculations and short memory consumption (resp. 8, - 16 and 32 integers coded on 64 bits. - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 - for an example. - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO: int = (1 << 64) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def getstate(self) -> List[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return self._state[:] - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module basexoroshiro.py ================================== - diff --git a/PyRandLib/cwg128.py b/PyRandLib/cwg128.py deleted file mode 100644 index 22fbab2..0000000 --- a/PyRandLib/cwg128.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg128( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 128-bits calculations and 128-bits output values with large - period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG - algorithms offer multi streams features, by simply using different initial - settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 96 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - Notice: in the original paper, four control value c[0] to c[3] are used. - It appears that these value are used just are 's' for c[0], 'x' for c[1], - 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg128() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 128 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO: int = (1 << 128) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & self._MODULO - self._weyl = (self._weyl + self._s) & self._MODULO - self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO - # returns the xored-shifted output value - return self._state ^ (self._a >> 96) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits - self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd - - else: - try: - self._a = _state[0] & self._MODULO - self._weyl = _state[1] & self._MODULO - self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd - self._state = _state[3] & self._MODULO - - except: - # uses local time as initial seed - self.setstate() - -#===== end of module cwg128.py ========================================= diff --git a/PyRandLib/cwg128_64.py b/PyRandLib/cwg128_64.py deleted file mode 100644 index 5f7a936..0000000 --- a/PyRandLib/cwg128_64.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg128_64( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 128-bits calculations and 64-bits output values with small - period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG - algorithms offer multi streams features, by simply using different - initial settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 48 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg128_64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff - self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff - self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff - # returns the xored-shifted output value - return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._s = initRand() | 1; # Notice: must be odd - self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits - - else: - try: - self._a = _state[0] & 0xffff_ffff_ffff_ffff - self._weyl = _state[1] & 0xffff_ffff_ffff_ffff - self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd - self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits - - except: - # uses local time as initial seed - self.setstate() - - -#===== end of module cwg128_64.py ====================================== diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py deleted file mode 100644 index 7b34c77..0000000 --- a/PyRandLib/cwg64.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple - -from .basecwg import BaseCWG -from .annotation_types import SeedStateType -from .splitmix import SplitMix64 - - -#============================================================================= -class Cwg64( BaseCWG ): - """ - Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators - dedicated to 64-bits calculations and 64-bits output values with small - period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG - algorithms offer multi streams features, by simply using different - initial settings for control value 's' - see below. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This CWG model evaluates pseudo-random numbers suites x(i) as a simple - mathematical function of - - x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) - - and returns as the output value the xored shifted: a >> 48 ^ x(i+1) - - where a, weyl and s are the control values and x the internal state of the - PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be - initialized each with any 64-bits value. - - See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator - with very low computation time, medium period, 64-bits output values and - very good randomness characteristics. - See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator - with very low computation time, medium period, 64- bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Cwg64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the CWGs that have - been implemented in PyRandLib, as presented in paper [8] - see file README.md. - - | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff - self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff - self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff - # returns the xored-shifted output value - return self._state ^ (self._a >> 48) - - - #------------------------------------------------------------------------- - def setstate(self, _state: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. If None, the local system time - is used instead. - """ - if _state is None or isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix64( _state ) - self._a = self._weyl = 0 - self._s = initRand() | 1; # Notice: must be odd - self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits - - else: - try: - self._a = _state[0] & 0xffff_ffff_ffff_ffff - self._weyl = _state[1] & 0xffff_ffff_ffff_ffff - self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd - self._state = _state[3] & 0xffff_ffff_ffff_ffff - - except: - # uses local time as initial seed - self.setstate() - - -#===== end of module cwg64.py ========================================== diff --git a/PyRandLib/fastrand32.py b/PyRandLib/fastrand32.py deleted file mode 100644 index 9813c66..0000000 --- a/PyRandLib/fastrand32.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselcg import BaseLCG -from .annotation_types import Numerical -from .splitmix import SplitMix32 - - -#============================================================================= -class FastRand32( BaseLCG ): - """ - Pseudo-random numbers generator - Linear Congruential Generator dedicated - to 32-bits calculations with very short period (about 4.3e+09) but very - short time computation. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. - - The implementation of this LCG 32-bits model is based on (a=69069, c=1) - since these two values have evaluated to be the 'best' ones for LCGs - within TestU01 while m = 2^32. - - See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with - low computation time also, longer period and quite better randomness - characteristics than for FastRand32. - - Furthermore this class is callable: - rand = FastRand32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = FastRand32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff - return self._state - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix32( _state ) - self._state = initRand() - else: - initRand = SplitMix32() - self._state = initRand() - -#===== end of module fastrand32.py ===================================== diff --git a/PyRandLib/fastrand63.py b/PyRandLib/fastrand63.py deleted file mode 100644 index 4483c40..0000000 --- a/PyRandLib/fastrand63.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselcg import BaseLCG -from .annotation_types import Numerical -from .splitmix import SplitMix63 - - -#============================================================================= -class FastRand63( BaseLCG ): - """ - Pseudo-random numbers generator - Linear Congruential Generator dedicated - to 63-bits calculations with very short period (about 9.2e+18) and short - time computation. - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i-1): x(i) = (a*x(i-1) + c) mod m - - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. - - The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) - since these two values have evaluated to be the 'best' ones for LCGs - within TestU01 while m = 2^63. - - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low - computation time but shorter period and worse randomness characteristics - than for FastRand63. - - Furthermore this class is callable: - rand = FastRand63() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = FastRand63() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | - | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 63 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff - return self._state - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance(_state, int) or isinstance(_state, float): - initRand = SplitMix63( _state ) - self._state = initRand() - else: - initRand = SplitMix63() - self._state = initRand() - - -#===== end of module fastrand63.py ===================================== diff --git a/PyRandLib/lfib116.py b/PyRandLib/lfib116.py deleted file mode 100644 index 8f1f000..0000000 --- a/PyRandLib/lfib116.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib116( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (8.3e+34). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = (x(i-24) + x(i-55)) mod 2^64 - - and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due - to the use of a 2^64 modulo and little memory space consumption (55 long integ- - ers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, - 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 17, 607 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib116() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib116() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-5 and i-17 -th values - k24 = self._index-24 - if k24 < 0: - k24 += LFib116._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib116._STATE_SIZE - - return myValue - -#===== end of module lfib116.py ======================================== diff --git a/PyRandLib/lfib1340.py b/PyRandLib/lfib1340.py deleted file mode 100644 index 5007045..0000000 --- a/PyRandLib/lfib1340.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib1340( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (2.4e+403). - - This module is part of library PyRandLib. - - Copyright (c) 2017-2025 Philippe Schmouker - - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = (x(i-861) + x(i-1279)) mod 2^64 - - and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time - due to the use of a 2^64 modulo but memory space consumption (1379 long integ- - ers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, - 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while - same computation time and far higher precision (64-bits calculations) than MRGs, - but memory consumption (resp. 17, 55 and 607 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib1340() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib1340() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-861 and i-1279 -th values - k861 = self._index-861 - if k861 < 0: - k861 += LFib1340._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib1340._STATE_SIZE - - return myValue - -#===== end of module lfib1340.py ====================================== diff --git a/PyRandLib/lfib668.py b/PyRandLib/lfib668.py deleted file mode 100644 index 0f5e494..0000000 --- a/PyRandLib/lfib668.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib668( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (1.2e+201). - - This module is part of library PyRandLib. - - Copyright (c) 2017-2025 Philippe Schmouker - - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = ( x(i-273) + x(i-607) ) mod 2^64 - - and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due - to the use of a 2^64 modulo but memory space consumption (607 long integers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, - 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 17, 55 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib668() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib668() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-273 and i-607 -th values - k273 = self._index-273 - if k273 < 0: - k273 += LFib668._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib668._STATE_SIZE - - return myValue - -#===== end of module lfib668.py ======================================= diff --git a/PyRandLib/lfib78.py b/PyRandLib/lfib78.py deleted file mode 100644 index 7f3589c..0000000 --- a/PyRandLib/lfib78.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .baselfib64 import BaseLFib64 - - -#============================================================================= -class LFib78( BaseLFib64 ): - """ - Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci - Generator with quite short period (3.0e+23). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be: - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in "TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, - August 2007". It is recommended to use such pseudo-random numbers generators - rather than LCG ones for serious simulation applications. - - The implementation of this LFib 64-bits model is based on a Lagged Fibonacci - generator (LFIB) that uses the recurrence - - x(i) = ( x(i-5) + x(i-17) ) mod 2^64 - - and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due - to the use of a 2^64 modulo and few memory space consumption (17 long integers). - - Please notice that the TestUO1 article states that the operator should be '*' - while Mascagni & Srinivasan in their original article stated that the operator is - '+'. We've implemented here the original operator: '+'. - - See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, - 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) - while same computation time and far higher precision (64-bits calculations) than - MRGs, but memory consumption (resp. 55, 607 and 1279 integers). - - Please notice that this class and all its inheriting sub-classes are callable. - Example: - - rand = LFib78() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = LFib78() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LFibs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | - | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | - | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | - | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-5 and i-17 -th values - k5 = self._index-5 - if k5 < 0: - k5 += LFib78._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % LFib78._STATE_SIZE - - return myValue - -#===== end of module lfib78.py ========================================= diff --git a/PyRandLib/melg19937.py b/PyRandLib/melg19937.py deleted file mode 100644 index 5d9b093..0000000 --- a/PyRandLib/melg19937.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg19937( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Furthermore, this class is callable: - rand = Melg19937() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg19937() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 312 - _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 311 - self._index = i_1 - - s311 = self._state[311] - - x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) - self._state[311] = s311 - - si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) - return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg44497.py b/PyRandLib/melg44497.py deleted file mode 100644 index 286cdd7..0000000 --- a/PyRandLib/melg44497.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg44497( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium - computation time and the equivalent of 21 32-bits integers memory little - consumption. - See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - - Furthermore, this class is callable: - rand = Melg444907() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg444907() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 696 - _A_COND = (0, 0x4fa9_ca36_f293_c9a9) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 695 - self._index = i_1 - - s695 = self._state[695] - - x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) - self._state[695] = s695 - - si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) - return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg607.py b/PyRandLib/melg607.py deleted file mode 100644 index 6a38a55..0000000 --- a/PyRandLib/melg607.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemelg import BaseMELG - - -#============================================================================= -class Melg607( BaseMELG ): - """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- - uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Maximally Equidistributed Long-period Linear Generators (MELG) use linear - recurrence based on state transitions with double feedbacks and linear output - transformations with several memory references. See reference [11] in README.md. - - MELGs offer large to very large periods with best known results in the evaluation - of their randomness. They ensure a maximally equidistributed generation of pseudo - random numbers. They pass all TestU01 tests and newer ones but are the slowest to - compute ones in the base of PRNGs that have been implemented in PyRandLib. - - Notice: the implementation of this version of the MELG algorithm in PyRandLib is - not as optimized as it is in C code provided by MELG authors. It is rather derived - from the formal description and related tables provided in paper referenced [11] - in file README.md, to be able to easier validate the Python code here. - - Notice also: in the original paper [11], in the description of Algorithm 1, an - error (typo) appears at the initialization of 'x'. An bit-xor operation appears - in the text while it should be a bit-or operation as explaind in plain text. We - correct in in the code here. - - See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), - same computation time and equivalent of 626 integers memory consumption. - See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar - computation time but use of even more memory space (equivalent of 1,393 32-bits - integers). This is the longest period version proposed in paper [11]. - - Furthermore, this class is callable: - rand = Melg607() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Melg607() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MELG algorithms that - have been implemented in PyRandLib, as provided in paper [11] and when available. - - | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | - | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constants - _STATE_SIZE: int = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 - _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Notice: the output value is coded on 64-bits. - """ - i = self._index - i_1 = (i + 1) % 9 - self._index = i_1 - - s9 = self._state[9] - - x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] - s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) # M=5, s1=13 - self._state[9] = s9 - - si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) # s2=35 - return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) # s3=30, L=3, b = 0x66ed... - - -#===== end of module melg607.py ======================================== diff --git a/PyRandLib/mrg1457.py b/PyRandLib/mrg1457.py deleted file mode 100644 index 17f8c32..0000000 --- a/PyRandLib/mrg1457.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg1457( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with long period (3.98e+438). - - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random - generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence - - x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation - time. - - See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (31-bits modulus) but use of more memory space (1597 - integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg1457() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers - _MODULO : int = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 31 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # The DX-47-3 version uses the recurrence - # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values - k1 = self._index-1 - if k1 < 0: - k1 = Mrg1457._STATE_SIZE - 1 - - k24 = self._index-24 - if k24 < 0: - k24 += Mrg1457._STATE_SIZE - - # then evaluates current value - myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 - self._state[self._index] = myValue - - # next index - self._index = (self._index + 1) % Mrg1457._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib/mrg287.py b/PyRandLib/mrg287.py deleted file mode 100644 index 119c3f0..0000000 --- a/PyRandLib/mrg287.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg287( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive - Generator with a long period (2.49e+86). - - This module is part of library PyRandLib. - - Copyright (c) 2016-2025 Philippe Schmouker - - Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 32-bits model is based on a Lagged Fibonacci - generator (LFIB), the Marsa-LFIB4 one. - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be - + (addition), - - (substraction), - * (multiplication), - ^ (bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) paper. - - The Marsa-LIBF4 version uses the recurrence - - x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due - to the use of a 2^32 modulo. - - See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower - computation time too (31-bits modulus) but use of more memory space (1_597 - integers). - - Furthermore, this class is callable: - rand = Mrg287() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg287() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO : int = 0xffff_ffff - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # The Marsa-LIBF4 version uses the recurrence - # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values - k55 = self._index-55 - if k55 < 0: - k55 += Mrg287._STATE_SIZE - - k119 = self._index-119 - if k119 < 0: - k119 += Mrg287._STATE_SIZE - - k179 = self._index-179 - if k179 < 0: - k179 += Mrg287._STATE_SIZE - - # then evaluates current value - myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % self._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib/mrg49507.py b/PyRandLib/mrg49507.py deleted file mode 100644 index 2eb2597..0000000 --- a/PyRandLib/mrg49507.py +++ /dev/null @@ -1,141 +0,0 @@ -""" -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class Mrg49507( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with a very long period (1.17e+14_903). - - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It - uses the recurrence - - x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) - - and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low - computation time. - - See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer - computation time (2^31-1 modulus calculations) but less memory space consumption - (47 integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - rand = Mrg49507() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Mrg49507() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the MRGs that have been - implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers - _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 31 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates indexes in suite for the i-7, i-1597 -th values - k7 = self._index-7 - if k7 < 0: - k7 += Mrg49507._STATE_SIZE - - # then evaluates current value - myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 - self._state[self._index] = myValue - - # next index - self._index = (self._index+1) % Mrg49507._STATE_SIZE - - # then returns the integer generated value - return myValue - -#===== end of module mrgrand49507.py =================================== diff --git a/PyRandLib/mrgrand1457.py b/PyRandLib/mrgrand1457.py deleted file mode 100644 index 45ba0c4..0000000 --- a/PyRandLib/mrgrand1457.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class MRGRand1457( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with long period (3.98e+438). - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random - generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence - - x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) - - and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation - time. - - See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See MRGRand49507 for a far longer period (2^49_507, i.e. 1.2e+14_903) with low - computation time too (31-bits modulus) but use of more memory space (1597 - integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - rand = MRGRand1457() - print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a uniform pseudo-random value within [a , b) - - Please notice that for simulating the roll of a dice you should program: - diceRoll = MRGRand1457() - print( int(diceRoll(1, 7)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRG - should definitively pass. - """ - - - #------------------------------------------------------------------------= - # 'protected' constant - _LIST_SIZE = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers - _MODULO = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG - - - #------------------------------------------------------------------------= - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - """ - # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values - k1 = self._index-1 - if k1 < 0: - k1 = MRGRand1457._LIST_SIZE - 1 - - k24 = self._index-24 - if k24 < 0: - k24 += MRGRand1457._LIST_SIZE - - # then evaluates current value - myValue = (67633152 * (self._list[k1] + self._list[k24] + self._list[self._index]) ) % 2_147_483_647 - self._list[self._index] = myValue - - # next index - self._index = (self._index + 1) % MRGRand1457._LIST_SIZE - - # then returns float value within [0.0, 1.0) - return myValue / 2_147_483_647.0 - - -#===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib/mrgrand287.py b/PyRandLib/mrgrand287.py deleted file mode 100644 index 8c07a4f..0000000 --- a/PyRandLib/mrgrand287.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basemrg import BaseMRG - - -#============================================================================= -class MRGRand287( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive - Generator with a long period (2.49e+86). - This module is part of library PyRandLib. - - Copyright (c) 2016-2021 Philippe Schmouker - - Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 32-bits model is based on a Lagged Fibonacci - generator (LFIB), the Marsa-LFIB4 one. - Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence - - x(i) = (x(i-r) op (x(i-k)) mod m - - where op is an operation that can be - + (addition), - - (substraction), - * (multiplication), - ^(bitwise exclusive-or). - - With the + or - operation, such generators are in fact MRGs. They offer very large - periods with the best known results in the evaluation of their randomness, as - stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) paper. - - The Marsa-LIBF4 version uses the recurrence - - x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due - to the use of a 2^32 modulo. - - See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and - longer computation time (2^31-1 modulus calculations) but less memory space - consumption (47 integers). - See MRGRand49507 for a far longer period (2^49_507, i.e. 1.2e+14_903) with low - computation time too (31-bits modulus) but use of more memory space (1_597 - integers). - - Furthermore this class is callable: - rand = MRGRand287() - print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a uniform pseudo-random value within [a , b) - - Notice that for simulating the roll of a dice you should program: - diceRoll = MRGRand287() - print( int(diceRoll(1, 7)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRG - should definitively pass. - """ - - #------------------------------------------------------------------------= - # 'protected' constant - _LIST_SIZE = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO = 4_294_967_295 # i.e. 0xffff_ffff, or (1<<32)-1, the modulo for DX-47-3 MRG - - - #------------------------------------------------------------------------= - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - """ - #The Marsa-LIBF4 version uses the recurrence - # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 - - # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values - k55 = self._index-55 - if k55 < 0: - k55 += MRGRand287._LIST_SIZE - - k119 = self._index-119 - if k119 < 0: - k119 += MRGRand287._LIST_SIZE - - k179 = self._index-179 - if k179 < 0: - k179 += MRGRand287._LIST_SIZE - - # then evaluates current value - myValue = (self._list[k55] + self._list[k119] + self._list[k179] + self._list[self._index]) % 4_294_967_295 - self._list[self._index] = myValue - - # next index - self._index = (self._index+1) % self._LIST_SIZE - - # then returns float value within [0.0, 1.0) - return myValue / 4_294_967_295.0 - -#===== end of module mrgrand287.py ================================== diff --git a/PyRandLib/mrgrand49507.py b/PyRandLib/mrgrand49507.py deleted file mode 100644 index 2f56fc9..0000000 --- a/PyRandLib/mrgrand49507.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Copyright (c) 2016-2022 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#----------------------------------------------------------------------------= -from .basemrg import BaseMRG - - -#----------------------------------------------------------------------------= -class MRGRand49507( BaseMRG ): - """ - Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive - Generator with a very long period (1.17e+14_903). - This module is part of library PyRandLib. - - Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random - numbers suites. Recurrence is of the form: - - x(i) = A * SUM[ x(i-k) ] mod M - - for 2 to more k different values. - - MRGs offer very large periods with the best known results in the evaluation of - their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - - The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It - uses the recurrence - - x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) - - and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low - computation time. - - See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and - longer computation time (2^31-1 modulus calculations) but less memory space - consumption (47 integers). - - Class random.Random is sub-subclassed here to use a different basic generator of - our own devising: in that case, overriden methods are: - random(), seed(), getstate(), and setstate(). - - Furthermore this class is callable: - rand = MRGRand49507() - print( rand() ) # prints a uniform pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a uniform pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a uniform pseudo-random value within [a , b) - - Notice that for simulating the roll of a dice you should program: - diceRoll = MRGRand49507() - print( int(diceRoll(1, 7)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the - inherited methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. - - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRG - should definitively pass. - """ - - #------------------------------------------------------------------------= - # 'protected' constant - _LIST_SIZE = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers - _MODULO = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG - - - #------------------------------------------------------------------------= - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - """ - # evaluates indexes in suite for the i-7, i-1597 -th values - k7 = self._index-7 - if k7 < 0: - k7 += MRGRand49507._LIST_SIZE - - # then evaluates current value - myValue = (-67108992 * (self._list[k7] + self._list[self._index])) % 2_147_483_647 - self._list[self._index] = myValue - - # next index - self._index = (self._index+1) % MRGRand49507._LIST_SIZE - - # then returns float value within [0.0, 1.0) - return myValue / 2_147_483_647.0 - -#----= end of module mrgrand49507.py ----------------------------------= diff --git a/PyRandLib/pcg1024_32.py b/PyRandLib/pcg1024_32.py deleted file mode 100644 index c721499..0000000 --- a/PyRandLib/pcg1024_32.py +++ /dev/null @@ -1,280 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .annotation_types import Numerical, SeedStateType, StateType -from .pcg64_32 import Pcg64_32 -from .splitmix import SplitMix32 - - -#============================================================================= -class Pcg1024_32( Pcg64_32 ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - extended by 1,024 equistributed generators, dedicated to 64-bits - calculations and 32-bits output with very large period (about 6.53e+9882) - but very short time computation. This version of the PCG algorithm gets - the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers - jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version - of th e PCG algorithm, as specified in the related paper (see [7] in - document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 - and the additional permutation output function and its internal multiple - states that implements its 1024-dimensionally equidistributed generator - directly coded in method 'next()'. - - See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low - computation time also and a longer period than for Pcg64_32, with 2 - 32-bits word integers memory consumption. Output values are returned on - 32 bits. - - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg1024_32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg1024_32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - _EXTENDED_STATE_SIZE: int = 1024 - - - #------------------------------------------------------------------------- - def __init__(self, _seed: SeedStateType = None) -> None: - """Constructor. - - Should _seed be None or not be of SeedStateType then the - local time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates a to-be-xor'ed 32-bits value from current extended state - if self._state & 0xffff_ffff == 0: - self._advancetable() - extendedValue = self._extendedState[ self._state & 0x03ff ] - - # then xor's it with the next 32-bits value evaluated with the internal state - return super().next() ^ extendedValue - - - #------------------------------------------------------------------------- - def getstate(self) -> StateType: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a list that contains self._STATE_SIZE integers. - """ - return [ self._extendedState[:], self._state ] - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: SeedStateType) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should - _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else - (e.g. None) then the shuffling of the local current time - value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState ) - - elif count == 2: - # each entry in _seedState[0] MUST be a 32-bits integer - # _seedState[1] MUST be a 64-bits integer - extendedCount = len( _seedState[0] ) - if extendedCount == self._EXTENDED_STATE_SIZE: - self._extendedState = _seedState[0][:] - self._state = _seedState[1] - elif extendedCount > 0: - extended = _seedState[0] - for s in _seedState[1:]: - extended ^= s - self._initextendedstate( extended ) - self._state = _seedState[1] - else: - self._initstate( _seedState[1] ) - - else: - self._initstate() - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _advancetable(self) -> None: - """Advances the extended states - """ - carry = False - for i, s in enumerate( self._extendedState ): - if carry: - carry = self._extendedstep(s, i) - if self._extendedstep(s, i): # notice: must be evaluated before carry is set - carry = True - - - #------------------------------------------------------------------------- - def _extendedstep(self, value: int, i: int) -> bool: - """Evaluates new extended state indexed value in the extended state table. - - Returns True when the evaluated extended value is set to zero on all bits - but its two lowest ones - these two bits never change with MCGs. - """ - state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff - state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) - state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff - - result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) - result ^= result >> 22 - - self._extendedState[i] = result - - return result == state & 0b11 - - - #------------------------------------------------------------------------- - def _initextendedstate(self, _initialSeed: Numerical = None) -> None: - """Inits the extended list of values. - - Inits the extended list of values according to some initial - seed that has to be an integer, or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - # feeds the list according to an initial seed. - initRand = SplitMix32( _initialSeed ) - self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal state of this PRNG. - - Inits its current state and its extended state also. The - initial seed has to be an integer, or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as the initial seed value. - The same initial seed is finally used to seed the current - state and the extended state. To avoid any unexpected - correlation between current state and any value of the - extended one, we use different PRNGs to seed the internal - state on one side and the extended state on the other side. - """ - super().setstate( _initialSeed ) # uses Pcg64_32() - self._initextendedstate( _initialSeed ) # uses Well1024a() - - - #------------------------------------------------------------------------- - @classmethod - def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: - """Evaluates the inversion of an xor-shift operation. - """ - if shift * 2 >= bitsCount: - return value ^ (value >> shift) - - botMask = (1 << (bitsCount - shift * 2)) - 1 - topMask = ~botMask & 0xffff_ffff - - top = (value ^ (value >> shift)) & topMask - - newBitsShift = bitsCount - shift - bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask - - return top | bot - - -#===== end of module pcg1024_32.py ===================================== diff --git a/PyRandLib/pcg128_64.py b/PyRandLib/pcg128_64.py deleted file mode 100644 index 6f9eb84..0000000 --- a/PyRandLib/pcg128_64.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basepcg import BasePCG -from .annotation_types import Numerical -from .splitmix import SplitMix64 - - -#============================================================================= -class Pcg128_64( BasePCG ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - dedicated to 128-bits calculations and 64-bits output with medium period - (about 3.40e+38) but very short time computation. The PCG algorithm - offers jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of - the PCG algorithm, as specified in the related paper (see [7] in document - README.md), so with a and c values coded on 128 bits (see method next()), - the modulo m = 2^128 and the additional permutation output function - directly implemented in method 'next()'. - - See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low - computation time also and a longer period than for Pcg64_32, with 2 - 32-bits word integers memory consumption. Output values are returned on - 32 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg128_64() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg128_64() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - _MODULO_128 : int = (1 << 128) - 1 - - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - current_state = self._state - self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & self._MODULO_128 - # the permutated output is then computed - random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state - current_state ^= current_state >> 64 # fixed shift XOR is then evaluated - value = current_state & 0xffff_ffff_ffff_ffff - rot_mask = (1 << random_rotation) - 1 - return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._state = _state & self._MODULO_128 - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._state = int( _state + 0.5 ) & self._MODULO_128 - else: - self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 - - else: - # uses local time as initial seed - initRand = SplitMix64() - self._state = (initRand() << 64) | initRand() - -#===== end of module pcg128_64.py ====================================== diff --git a/PyRandLib/pcg64_32.py b/PyRandLib/pcg64_32.py deleted file mode 100644 index 1fa867e..0000000 --- a/PyRandLib/pcg64_32.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basepcg import BasePCG -from .annotation_types import Numerical -from .splitmix import SplitMix64 - - -#============================================================================= -class Pcg64_32( BasePCG ): - """ - Pseudo-random numbers generator - Permutated Congruential Generator - dedicated to 64-bits calculations and 32-bits output with medium period - (about 1.84e+19) but very short time computation. The PCG algorithm - offers jump ahead and multi streams features. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- - atical function of - - x(i) = (a * x(i-1) + c) mod m - - as are LCGs, but associated with a permutation of a subpart of the bits of - the internal state of the PRNG. The output of PCGs is this permutated - subpart of its internal state, leading to a very large enhancement of the - randomness of these algorithms compared with the LCGs one. - - These PRNGs have been tested with TestU01 and have shown to pass all tests - (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: - A C Library for Empirical Testing of Random Number Generators - ACM - Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') - - PCGs are very fast generators, with low memory usage except for a very few - of them and medium to very large periods. They offer jump ahead and multi - streams features for most of them. They are difficult to very difficult to - invert and to predict. - - The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the - PCG algorithm, as specified in the related paper (see [7] in document - README.md), so with a = 6364136223846793005, c = 1442695040888963407, the - modulo m = 2^64 and the additional permutation output function directly - implemented in method 'next()'. - - See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with - low computation time also and a longer period than for Pcg64_32, with 4 - 32-bits word integers memory consumption. Output values are returned on - 64 bits. - - See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator - with low computation time also and a very large period, but 1,026 32-bits - word integers memory consumption. Output values are returned on 32 bits. - - Furthermore this class is callable: - rand = Pcg64_32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Pcg64_32() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Reminder: - We give you here below a copy of the table of tests for the PCGs that have - been implemented in PyRandLib, as provided by the author of PCGs - see - reference [7] in file README.md. - - | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | - | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | - | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | - | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None or not a numerical then the local - time is used (with its shuffled value) as a seed. - """ - super().__init__( _seed ) # this call creates attribute self._state and sets it - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - # evaluates next internal state - current_state = self._state - self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff - # the permutated output is then computed - random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state - current_state ^= current_state >> 22 # fixed shift XOR is then evaluated - return (current_state >> (22 + random_shift)) & 0xffff_ffff - - - #------------------------------------------------------------------------- - def setstate(self, _state: Numerical) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call - to getstate(), and setstate() restores the internal - state of the generator to what it was at the time - setstate() was called. - """ - if isinstance( _state, int ): - # passed initial seed is an integer, just uses it - self._state = _state & 0xffff_ffff_ffff_ffff - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff - else: - self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff - - else: - # uses local time as initial seed - initRand = SplitMix64() - self._state = initRand() - -#===== end of module pcg64_32.py ======================================= diff --git a/PyRandLib/splitmix.py b/PyRandLib/splitmix.py deleted file mode 100644 index 2d674e8..0000000 --- a/PyRandLib/splitmix.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -import time - -from .annotation_types import Numerical - - -#============================================================================= -class SplitMix64: - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 64 bits. It implements the - 64-bits version of the Fast Splittable Pseudorandom Number - Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine - H. Flood in "Fast splittable pseudorandom number generators.", in - ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. - - It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) - in 2015, provided under the Creative Commons license and modified - under the same license by D. Lemire by Aug. 2017. - (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - if isinstance( _seed, int ): - self.state = self.__call__( _seed ) - - elif isinstance( _seed, float ): - # transforms passed initial seed from float to integer - if _seed < 0.0 : - _seed = -_seed - - if _seed >= 1.0: - self._state = self.__call__( round(_seed) ) - else: - self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) - - else: - # uses system local time - self._state = self.__call__( int(time.time() * 1000.0) ) - - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - if _seed is not None: - self._state = _seed & 0xffff_ffff_ffff_ffff - - self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff - self._state &= 0xffff_ffff_ffff_ffff - - z = self._state - z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff - z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff - - return z ^ (z >> 31) - - -#============================================================================= -class SplitMix63( SplitMix64 ): - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 63 bits. - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - super().__init__( _seed ) - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - # returns the 63 higher bits generated by base class operator () - return super().__call__( _seed ) >> 1 - - -#============================================================================= -class SplitMix32( SplitMix64 ): - """The splitting and mixing algorithm used to initialize internal states of PRNGs. - - This class and its inheriting classes are only provided for the - initialization of the internal state of all other PRNGs. It SHOULD - NOT BE USED as a generic PRNG due to is randomness big limitations. - - This class evaluates "random" values on 32 bits. - """ - #------------------------------------------------------------------------- - def __init__(self, _seed: Numerical = None) -> None: - """Constructor. - - Should _seed be None, the internal system local time is used as the initial seed - """ - super().__init__( _seed ) - - #------------------------------------------------------------------------- - def __call__(self, _seed: int = None) -> int: - """The split-mix algorithm. - """ - # returns the 32 higher bits generated by base class operator () - return super().__call__( _seed ) >> 32 diff --git a/PyRandLib/squares32.py b/PyRandLib/squares32.py deleted file mode 100644 index f916093..0000000 --- a/PyRandLib/squares32.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basesquares import BaseSquares -from .annotation_types import SeedStateType, StatesList - - -#============================================================================= -class Squares32( BaseSquares ): - """ - Pseudo-random numbers generator - Squares pseudo-random Generators - dedicated to 64-bits calculations and 32-bits output values with - small period (min 2^64, i.e. 1.84e+19) but short computation time. - All Squares algorithms offer multi streams features, by simply - using different initial settings for control value 'key'. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This Squares models is based on a four rounds of squaring and - exchanging of upper and lower bits of the successive combinations. - Output values are provided on 32-bits or on 64-bits according to - the model. See [9] in README.md. - - See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 64-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Squares32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Return a 32-bits value. - """ - self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff - - y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff - z = (y + self._key) & 0xffff_ffff_ffff_ffff - # round 1 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 2 - x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 3 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 4 - return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 - -#===== end of module squares32.py ====================================== diff --git a/PyRandLib/squares64.py b/PyRandLib/squares64.py deleted file mode 100644 index 573f2e2..0000000 --- a/PyRandLib/squares64.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basesquares import BaseSquares -from .annotation_types import SeedStateType, StatesList - - -#============================================================================= -class Squares64( BaseSquares ): - """ - Pseudo-random numbers generator - Squares pseudo-random Generators - dedicated to 64-bits calculations and 32-bits output values with - small period (min 2^64, i.e. 1.84e+19) but short computation time. - All Squares algorithms offer multi streams features, by simply - using different initial settings for control value 'key'. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - This Squares models is based on a four rounds of squaring and - exchanging of upper and lower bits of the successive combinations. - Output values are provided on 32-bits or on 64-bits according to - the model. See [9] in README.md. - Caution: this 64-bits output values version should not pass the - birthday test, which is a randmoness issue, while this is not - mentionned in the original paper (see [9] in file README.md). - - See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with - low computation time, medium period, 32-bits output values and - very good randomness characteristics. - - Furthermore this class is callable: - rand = Squares32() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the Squares - that have been implemented in PyRandLib, as presented in paper [9] - - see file README.md. - - | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - - #------------------------------------------------------------------------- - _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. It is THE multiplier constant value to be applied to - pseudo-random number for them to be normalized in interval [0.0, 1.0). - """ - - _OUT_BITS: int = 64 - """The value of this class attribute MUST BE OVERRIDDEN in inheriting - classes if returned random integer values are coded on anything else - than 32 bits. - """ - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: SeedStateType = None) -> None: - """Constructor. - - Should _seedState be None then the local time is used as a seed (with - its shuffled value). - Notice: method setstate() is not implemented in base class BaseRandom. - So, it must be implemented in classes inheriting BaseLCG and it must - initialize attribute self._state. - """ - super().__init__( _seedState ) # this internally calls 'setstate()' which - # MUST be implemented in inheriting classes - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - - Returns a 64-bits value. - """ - self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff - - y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff - z = (y + self._key) & 0xffff_ffff_ffff_ffff - # round 1 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 2 - x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 3 - x = (x * x + y) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 4 - t = x = (x * x + z) & 0xffff_ffff_ffff_ffff - x = (x >> 32) | ((x & 0xffff_ffff) << 32) - # round 5 - return t ^ (((x * x + y) >> 32) & 0xffff_ffff) - -#===== end of module squares64.py ====================================== diff --git a/PyRandLib/types.py b/PyRandLib/types.py deleted file mode 100644 index cabf22b..0000000 --- a/PyRandLib/types.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Copyright (c) 2021-2022 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import List, Tuple, Union - -Numerical = Union[ int, float ] -StateType = Union[ Tuple[Numerical], List[Numerical], Tuple[List[Numerical], int] ] -SeedStateType = Union[ Numerical, StateType ] - - -#===== end of PyRandLib.types =====# diff --git a/PyRandLib/well1024a.py b/PyRandLib/well1024a.py deleted file mode 100644 index 9f92c62..0000000 --- a/PyRandLib/well1024a.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well1024a( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well1024a() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well1024a() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - i_1 = (i - 1) & 0x1f - - z0 = self._state[i_1] - # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great - # simplification for the implementation of the generic WELL algorithm when evaluating z0. - z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) - # notice: the transformation applied to self._state[i] for Well1024a - # is the identity which leads to simplification also - z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) - # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a - # version, the zero matrix _M0 which we suppress here for calculations optimization purpose - - self._index = i_1 - return z3 - -#===== end of module well1024a.py ====================================== diff --git a/PyRandLib/well19937c.py b/PyRandLib/well19937c.py deleted file mode 100644 index 7b836e0..0000000 --- a/PyRandLib/well19937c.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well19937c( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well19937c() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well19937c() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - if i >= 2: - i_1, i_2 = i - 1, i - 2 - elif i == 1: - i_1, i_2 = 0, 623 - else: - i_1, i_2 = 623, 622 - - z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) - z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) - z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) - self._index = i_1 - - return self._tempering(z3, 0xe46e1700, 0x9b868000) - -#===== end of module well19937c.py ===================================== diff --git a/PyRandLib/well44497b.py b/PyRandLib/well44497b.py deleted file mode 100644 index 0157139..0000000 --- a/PyRandLib/well44497b.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well44497b( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well44497b() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well44497b() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - if i >= 2: - i_1, i_2 = i - 1, i - 2 - elif i == 1: - i_1, i_2 = 0, 1390 - else: - i_1, i_2 = 1390, 1389 - - z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) - z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) - z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 - self._index = i_1 - - return self._tempering(z3, 0x93dd1400, 0xfa118000) - -#===== end of module Well44497b.py ===================================== diff --git a/PyRandLib/well512a.py b/PyRandLib/well512a.py deleted file mode 100644 index 42b14a0..0000000 --- a/PyRandLib/well512a.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from .basewell import BaseWELL - -#============================================================================= -class Well512a( BaseWELL ): - """ - Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed - Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence - based on primitive characteristic polynomials associated with left- and right- - shifts and xor operations to fastly evaluate pseudo-random numbers suites. - - WELLs offer large to very large periods with best known results in the evaluation - of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and - Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical Software, - vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random - numbers generators rather than LCG ones for serious simulation applications. - Furthermore, WELLs have proven their great ability to very fastly escape from - zeroland. - - Notice: the algorithm in its Well512a version has been coded here as a direct - implementation of its description in the initial paper: "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. - (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). - As such, only minimalist optimization has been coded, with the aim at easing the - verification of its proper implementation. - - See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low - computation time and 16 integers memory consumption. - See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same - computation time and 32 integers memory consumption. - See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar - computation time but use of more memory space (624 integers). - See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar - computation time but use of even more memory space (1,391 integers). - - Furthermore, this class is callable: - rand = Well512a() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Notice that for simulating the roll of a dice you should program: - diceRoll = Well512a() - print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - - - Such a programming is an accelerated while still robust emulation of the inherited - methods: - - random.Random.randint(self,1,6) and - - random.Random.randrange(self,1,7,1) - - Reminder: - We give you here below a copy of the table of tests for the WELL algorithms that - have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when - available. - - | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | - - (1)The Well19937c generator provided with library PyRandLib implements the - Well19937a algorithm augmented with an associated tempering algorithm. - This should very slightly slow down its CPU performance while enhancing - its pseudo-randomness quality, as measured by TestU01. - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - # 'protected' constant - _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. - """ - i = self._index - i_1 = (i - 1) & 0xf - - z0 = self._state[i_1] - # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great - # simplification for the implementation of the generic WELL algorithm when evaluating z0. - z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) - z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) - # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a - # version, the zero matrix _M0 which we suppress here for calculations optimization purpose - z3 = z1 ^ z2 - - self._state[i] = z3 - self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) - - self._index = i_1 - return z3 - -#===== end of module well512a.py ======================================= diff --git a/PyRandLib/xoroshiro1024.py b/PyRandLib/xoroshiro1024.py deleted file mode 100644 index 62d5cb6..0000000 --- a/PyRandLib/xoroshiro1024.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, SeedStateType, StatesListAndExt -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro1024( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from - zeroland (100 iterations) and passes TestU01 tests. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro1024** of the algorithm. - - See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear - PRNG, with low computation time, 64-bits output values and good randomness - characteristics. - See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Furthermore this class is callable: - rand = Xoroshiro1024() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - #------------------------------------------------------------------------- - _STATE_SIZE : int = 16 - _SIZE_MODULO: int = 0xf # optimization here, to use operand & - - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - ''' - const int q = p; - const uint64_t s0 = s[p = (p + 1) & 15]; - uint64_t s15 = s[q]; - const uint64_t result_plus = s0 + s15; - const uint64_t result_plusplus = rotl(s0 + s15, R) + s15; - const uint64_t result_star = s0 * S; - const uint64_t result_starstar = rotl(s0 * S, R) * T; - s15 ^= s0; - s[q] = rotl(s0, A) ^ s15 ^ (s15 << B); - s[p] = rotl(s15, C); - ''' - previousIndex = self._index - # advances the internal state of the PRNG - self._index += 1 - self._index &= self._SIZE_MODULO - sLow = self._state[ self._index ] - sHigh = self._state[ previousIndex ] ^ sLow - self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) - self._state[ self._index ] = self._rotleft( sHigh, 36 ) - # returns the output value - return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: SeedStateType = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._index = 0 - self._initstate() - - elif count == 1: - self._index = 0 - self._initstate( _seedState[0] ) - - else: - self._initindex( _seedState[1] ) - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero - else: - self._initstate( _seedState[0] ) - - except: - self._index = 0 - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initindex(self, _index: int) -> None: - """Inits the internal index pointing to the internal list. - """ - try: - self._index = int( _index ) & self._SIZE_MODULO - except: - self._index = 0 - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(self._STATE_SIZE) ] - - -#===== end of module xoroshiro1024.py ================================== - diff --git a/PyRandLib/xoroshiro256.py b/PyRandLib/xoroshiro256.py deleted file mode 100644 index 0b09b63..0000000 --- a/PyRandLib/xoroshiro256.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, StatesList -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro256( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from - zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats - flaws, with a bad Hamming weight near zero (see - https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro256** of the algorithm. - - See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Implementation notice: the internal state of this PRNG is coded on four integers - rather than on a list of four integers, to optimize computations time. - - Furthermore this class is callable: - rand = Xoroshiro256() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - currentS1 = self._s1 - # advances the internal state of the PRNG - self._s2 ^= self._s0 - self._s3 ^= self._s1 - self._s1 ^= self._s2 - self._s0 ^= self._s3 - self._s2 ^= (currentS1 << 17) & self._MODULO - self._s3 = self._rotleft( self._s3, 45 ) - # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # each entry in _seedState MUST be integer - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._s0 = initRand() - self._s1 = initRand() - self._s2 = initRand() - self._s3 = initRand() - - -#===== end of module xoroshiro256.py =================================== - diff --git a/PyRandLib/xoroshiro512.py b/PyRandLib/xoroshiro512.py deleted file mode 100644 index 73ed1c8..0000000 --- a/PyRandLib/xoroshiro512.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from typing import Tuple, Union - -from .basexoroshiro import BaseXoroshiro -from .annotation_types import Numerical, StatesList -from .splitmix import SplitMix64 - - -#============================================================================= -class Xoroshiro512( BaseXoroshiro ): - """The base class for all xoroshiro PRNGs. - - Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random - generator, the four 64-bits integers state array version of the Scrambled Linear - Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium - period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from - zeroland (30 iterations only) and passes TestU01 tests. - - This module is part of library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The base xoroshiro linear transformation is obtained combining a rotation, a - shift, and again a rotation. An additional scrambling method based on two - multiplications is also computed for this version xoroshiro512** of the algorithm. - - See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear - PRNG, with low computation time, 64-bits output values and good randomness - characteristics. - See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear - PRNG, with low computation time, 64-bits output values and very good randomness - characteristics. - - Furthermore this class is callable: - rand = Xoroshiro512() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a - print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) - - Reminder: - We give you here below a copy of the table of tests for the xoroshiros that have - been implemented in PyRandLib, as described by the authors of xoroshiro - see - reference [10] in file README.md. - - | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | - | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | - | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | - - * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRNG; - * _crush_ is a bigger set of tests that test more deeply expected random - characteristics; - * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG - should definitively pass. - """ - - #------------------------------------------------------------------------- - def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: - """Constructor. - - _seedState is either a valid state, an integer, a float or None. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value - being then in range (0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. - """ - super().__init__( _seedState ) - # this call creates the two attributes - # self._state and self._index, and sets them - # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def next(self) -> int: - """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. - """ - currentS1 = self._state[1] - # advances the internal state of the PRNG - self._state[2] ^= self._state[0] - self._state[5] ^= self._state[1] - self._state[1] ^= self._state[2] - self._state[7] ^= self._state[3] - self._state[3] ^= self._state[4] - self._state[4] ^= self._state[5] - self._state[0] ^= self._state[6] - self._state[6] ^= self._state[7] - self._state[6] ^= (currentS1 << 11) & self._MODULO - self._state[7] = self._rotleft( self._state[7], 21 ) - # returns the output value - return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO - - - #------------------------------------------------------------------------- - def getstate(self) -> Tuple[ int ]: - """Returns an object capturing the current internal state of the generator. - - This object can be passed to setstate() to restore the state. - It is a tuple containing a list of self._STATE_SIZE integers. - """ - return (self._s0, self._s1, self._s2, self._s3) - - - #------------------------------------------------------------------------- - def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: - """Restores the internal state of the generator. - - _seedState should have been obtained from a previous call to - getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a list of self._STATE_SIZE - integers (64-bits). Should _seedState be a sole integer or - float then it is used as initial seed for the random filling - of the internal list of self._STATE_SIZE integers. Should - _seedState be anything else (e.g. None) then the shuffling of - the local current time value is used as such an initial seed. - """ - try: - count = len( _seedState ) - - if count == 0: - self._initstate() - - elif count == 1: - self._initstate( _seedState[0] ) - - else: - if (len(_seedState[0]) == self._STATE_SIZE): - self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero - else: - self._initstate( _seedState[0] ) - - except: - self._initstate( _seedState ) - - - #------------------------------------------------------------------------- - def _initstate(self, _initialSeed: Numerical = None) -> None: - """Inits the internal list of values. - - Inits the internal list of values according to some initial - seed that has to be an integer or a float ranging within - [0.0, 1.0). Should it be None or anything else then the - current local time value is used as initial seed value. - """ - initRand = SplitMix64( _initialSeed ) - self._state = [ initRand() for _ in range(8) ] - - -#===== end of module xoroshiro512.py =================================== - diff --git a/testCPUPerfs.py b/testCPUPerfs.py deleted file mode 100644 index 2c7b532..0000000 --- a/testCPUPerfs.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -import sys -from time import perf_counter_ns -from timeit import repeat - -from PyRandLib import * - - -#============================================================================= -def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): - """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" - print("---", prng_class_name, "---") - perfs = repeat("rnd.next()", - setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", - repeat=n_repeats, - timer=perf_counter_ns, - number=n_loops) - print([1e-9 * p / n_loops for p in perfs]) - print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") - - -#============================================================================= -if __name__ == "__main__": - - print("=== PyRandLib CPU time performances ===") - print("Python version:", sys.version, '\n') - - N = 15 - - test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) - test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) - test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) - test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) - test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) - test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) - test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) - test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) - test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) - test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) - test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) - - -#===== end of module testCPUPerfs.py =================================== diff --git a/testED.py b/testED.py deleted file mode 100644 index 91a4a67..0000000 --- a/testED.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -#============================================================================= -from math import sqrt -from statistics import mean, median, stdev -from PyRandLib import * - - -#============================================================================= -def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): - """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. - - This module is provided with library PyRandLib. - - Copyright (c) 2025 Philippe Schmouker - - The Pseudo-Random Numbers Generators implemented in library PyRandLib have - been chosen as being the best in class ones about their randmoness quality - - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard - Simard (Université de Montréal) in 'TestU01: A C Library for Empirical - Testing of Random Number Generators - ACM Transactions on Mathematical - Software, vol.33 n.4, pp.22-40, August 2007'). - - One of the main characteristics of these PRNGs is the equidistribution of - the generated random numbers. Validating this equidistribution does not - ensure the correctness of any implementation BUT the failure of this - validation ensures a not correct implementation. This is the sole goal of - this litle script. - - This script runs an N-times loop on each algprithm. At each loop, it draws - a pseudo-random number in the interval [0; 1,000) and sets an histogram of - the drawings (1,000 entries). It then evaluates statistics values mean, - median and standard eviation for each histogram and, for each histogram - entry, evaluates its variance. Should mean value be far from N / 1,000 or - any variance get a too large value, the script outputs on console all - faulty values. - """ - algo_name = rnd_algo.__class__.__name__ - print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') - - hist = [0]*nb_entries - - expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean - expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation - expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance - - if expected_max_diff_mean_median < 0.5: - expected_max_diff_mean_median= 0.5 - - for _ in range(nb_loops): - n = int(rnd_algo() * nb_entries) - hist[n] += 1 - - # uncomment next line if you want to print the content of the histograms - #print(hist, '\n') - - print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") - mn, md, st = mean(hist), median(hist), stdev(hist) - print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") - - err = False - - if (abs(md - mn) > expected_max_diff_mean_median): - err = True - print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") - if (st > expected_max_stdev): - err = True - print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") - - min_variance = max_variance = 0.0 - - for i in range(nb_entries): - variance = (hist[i] - mn) / st - if abs(variance) > expected_max_variance: - print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") - err = True - if variance < min_variance: - min_variance = variance - elif variance > max_variance: - max_variance = variance - - print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') - print(f", min: {min(hist)}, max: {max(hist)}") - - if (not err): - print(" Test OK.") - print() - - -#============================================================================= -if __name__ == "__main__": - test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number - test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) - test_algo(Cwg128(), 3217, nb_loops = 2_000_000) - test_algo(FastRand32(), 3217, nb_loops = 2_000_000) - test_algo(FastRand63(), 3217, nb_loops = 2_000_000) - test_algo(LFib78(), 3217, nb_loops = 2_000_000) - test_algo(LFib116(), 3217, nb_loops = 2_000_000) - test_algo(LFib668(), 3217, nb_loops = 2_000_000) - test_algo(LFib1340(), 3217, nb_loops = 2_000_000) - test_algo(Melg607(), 3217, nb_loops = 2_000_000) - test_algo(Melg19937(), 3217, nb_loops = 2_000_000) - test_algo(Melg44497(), 3217, nb_loops = 2_000_000) - test_algo(Mrg287(), 3217, nb_loops = 2_000_000) - test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) - test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) - test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) - test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) - test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) - test_algo(Squares32(), 3217, nb_loops = 2_000_000) - test_algo(Squares64(), 3217, nb_loops = 2_000_000) - test_algo(Well512a(), 3217, nb_loops = 1_500_000) - test_algo(Well1024a(), 3217, nb_loops = 1_500_000) - test_algo(Well19937c(), 2029) # notice: 2029 is a prime number - test_algo(Well44497b(), 2029) - test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) - test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) - test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) - - - -#===== end of module testED.py ========================================= From fbdbc982428ebb9b5859d40a4df5ced5a95b62c8 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 7 Mar 2025 19:34:10 +0100 Subject: [PATCH 3/3] #152-doc review - README.md Fixed typos. Notice: not included in release v2.0.0 --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4ba4951..75d7800 100644 --- a/README.md +++ b/README.md @@ -594,28 +594,28 @@ Since the base class **BaseRandom** inherits from the built-in class `random.Ran **betavariate**(self, alpha, beta) Beta distribution. -Conditions on the parameters are alpha > 0 and beta > 0. +Conditions on the parameters are `alpha > 0` and `beta > 0`. Returned values range between 0 and 1. **binomialvariate**(self, n=1, p=0.5) -Binomial distribution. Return the number of successes for n independent trials with the probability of success in each trial being p: +Binomial distribution. Return the number of successes for `n` independent trials with the probability of success in each trial being `p`: Mathematically equivalent to: sum(random() < p for i in range(n)) -The number of trials n should be a non-negative integer. The probability of success p should be between 0.0 <= p <= 1.0. The result is an integer in the range 0 <= X <= n. This built-in, method has been added since Python 3.12. **PyRandLIb** implements it also for all former versions of Python: -3.6, -3.9, -3.10, and -3.11. +The number of trials `n` should be a non-negative integer. The probability of success `p` should be between `0.0 <= p <= 1.0`. The result is an integer in the range `0 <= X <= n`. This built-in method has been added since Python 3.12. **PyRandLIb** implements it also for all former versions of Python: -3.6, -3.9, -3.10, and -3.11. **choice**(self, seq) -Chooses a random element from a non-empty sequence. 'seq' has to be non empty. +Chooses a random element from a non-empty sequence. `seq` has to be non empty. **choices**(population, weights=None, *, cum_weights=None, k=1) -Returns a *k* sized list of elements chosen from the population, with replacement. If the population is empty, raises IndexError. +Returns a `k` sized list of elements chosen from the population, with replacement. If the population is empty, raises `IndexError`. -If a *weights* sequence is specified, selections are made according to the relative weights. Alternatively, if a *cum_weights* sequence is given, the selections are made according to the cumulative weights (perhaps computed using `itertools.accumulate()`). +If a `weights` sequence is specified, selections are made according to the relative weights. Alternatively, if a `cum_weights` sequence is given, the selections are made according to the cumulative weights (perhaps computed using `itertools.accumulate()`). For example, the relative weights `[10, 5, 30, 5]` are equivalent to the cumulative weights `[10, 15, 45, 50]`. Internally, the relative weights are converted to cumulative weights before making selections, so supplying the cumulative weights saves work. @@ -631,7 +631,7 @@ Exponential distribution. `lambd` is 1.0 divided by the desired mean. It should be nonzero. (The parameter should be called "lambda", but this is a reserved word in Python). Returned values range from 0 to positive infinity if `lambd` is positive, and from negative infinity to 0 if `lambd` is negative. -Since Python 3.12, the parameter `lambd` gets a default value in this built-in method. **PyRandLib** defines then this method for all former versions of Pyhton : -3.6, -3.9, -3.10 and -3.11. +Since Python 3.12, the parameter `lambd` gets a default value in this built-in method. **PyRandLib** defines then this method for all former versions of Python : -3.6, -3.9, -3.10 and -3.11. **gammavariate**(self, alpha, beta) @@ -643,8 +643,8 @@ 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. +`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. @@ -675,7 +675,7 @@ Pareto distribution. `alpha` is the shape parameter. **randint**(self, a, b) -Returns a random integer in range [a, b], including both end points. +Returns a random integer in range `[a, b]`, including both end points. **randrange**(self, stop) @@ -710,9 +710,9 @@ Restores internal state from object returned by `getstate()`. **shuffle**(self, x, random=None) -Shuffle the sequence x in place. Returns None. +Shuffle the sequence `x` in place. Returns None. -The optional argument `random` is a 0-argument function returning a random float in [0.0, 1.0); by default, this is the function random(). +The optional argument `random` is a 0-argument function returning a random float in `[0.0, 1.0)`; by default, this is the function random(). To shuffle an immutable sequence and return a new shuffled list, use `sample(x, k=len(x))` instead. @@ -728,7 +728,7 @@ see [http://en.wikipedia.org/wiki/Triangular_distribution](http://en.wikipedia.o **uniform**(self, a, b) -Gets a random number in the range [`a`, `b`) or [`a`, `b`] depending on rounding. +Gets a random number in the range `[a, b)` or `[a, b]` depending on rounding. **vonmisesvariate**(self, mu, kappa)