From 738db9500b36a75d9edf59ca7474f3d262d0169c Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 12:40:49 +0100 Subject: [PATCH 01/12] temporary commit --- PyRandLib/LICENSE | 21 -- PyRandLib/README.md | 686 -------------------------------------------- README.md | 649 ----------------------------------------- 3 files changed, 1356 deletions(-) delete mode 100644 PyRandLib/LICENSE delete mode 100644 PyRandLib/README.md delete mode 100644 README.md diff --git a/PyRandLib/LICENSE b/PyRandLib/LICENSE deleted file mode 100644 index ccab7fa..0000000 --- a/PyRandLib/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -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. diff --git a/PyRandLib/README.md b/PyRandLib/README.md deleted file mode 100644 index 9731cf5..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-2025 Philippe Schmouker, - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION 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/README.md b/README.md deleted file mode 100644 index 94f7926..0000000 --- a/README.md +++ /dev/null @@ -1,649 +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-2025 Philippe Schmouker, - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION 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 Numbers Generator(PRNG) implemented in **PyRandLib** is self documented. Names of classes directly refer to the type of PRNG they implement augmented with some number characterizing their periodicity. All of their randomness characteristics are explained in every related module. - - -### 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. - -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 PRG but **PyRandLib** implements many other PRNGs that are either far faster or far better in terms of generated pseudo-randomness than the Mersenne twister PRG. - - - -## Installation -Currently, the only way to install **PyRandLib** is to download the `.zip` or `.tar.gz` archive, then to directly put sub-directory `PyRandLib\` from archive into directory `Lib\site-packages\` of your Python environment. See https://schmouk.github.io/PyRandLib/ for an easy access to download versions or click on tab **releases** on the home page of this GitHub repository. - -A distribution version (to be installed via pip or easy-install in cmd tool or in console) is to come (no date yet). - - - -## Randomness evaluation -In [1], every known PRNG at the time of the editing has been tested according to three different sets of tests: -* **_small crush_** is a small set of simple tests that quickly tests some of the expected characteristics for a pretty good PRNG; -* **_crush_** is a bigger set of tests that test more deeply expected random characteristics; -* **_big crush_** is the ultimate set of difficult tests that any **good** PRNG should definitively pass. - -We give you here below a copy of the resulting table for the PRGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. - - | 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 | - | 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 | - | 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 | - | 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. | - | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19937 | 4.30 | 1.6 | 0 | 2 | 2 | - -(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. - - - -## CPU Performances - Times evaluation - -The above table provided times related to the C implementation of the specified PRNGs as measured with TestU01 [1] by the authors of the paper. -We provide in the table below the evaluation of times spent in calling the `__call__()` method for all PRNGs implemented in library **PyRandLib**. Then, the measured elapsed time includes the calling and returning Python mechanisms and not only the computation time of the sole algorithm code. This is the duration of interest to you since this is the main use of the library you will have. It only helps comparing the performances between the implemented PRNGs. - -We currently provide them as tested with Python 3.9. We will further provide them for every version of Python above 3.9. Time unit is microsecond. Tests have been run on an Intel(R) Core(TM) i5-1035G1 CPU @ 1.00 GHz, 1190 MHz, 4 cores, 8 logical processors, 64-bits, with 8 GB RAM and over Microsoft Windows 11 ed. Family. -The evaluation script is provided at the root of this repository: `testCPUPerfs.py`. - -Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environment. Measurements with next versions of Python are to come. - -**PyRandLib** time-64 bits: - | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | - | FastRand32 | 0.91 | | | | | 11 | 106 | *too many* | - | FastRand63 | 0.97 | | | | | 0 | 5 | 7 | - | LFib78 | 1.08 | | | | | 0 | 0 | 0 | - | LFib116 | 1.10 | | | | | 0 | 0 | 0 | - | LFib668 | 1.12 | | | | | 0 | 0 | 0 | - | LFib1340 | 1.12 | | | | | 0 | 0 | 0 | - | MRGRand287 | 1.41 | | | | | 0 | 0 | 0 | - | MRGRand1457 | 1.13 | | | | | 0 | 0 | 0 | - | MRGRand49507 | 1.30 | | | | | 0 | 0 | 0 | - | Well512a | 2.79 | | | | | n.a. | n.a. | n.a. | - | Well1024a | 2.60 | | | | | 0 | 4 | 4 | - | Well19937b (1) | 3.25 | | | | | 0 | 2 | 2 | - | Well44497c | 3.69 | | | | | n.a. | n.a. | n.a. | - - (*missing values in empty columns are to come*) - - -## Implementation -Current implementation of **PyRandLib** uses Python 3.x with no Cython version. -It has been tested with Python 3.8 but should run with all of Python 3. - -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 ) ) - - -## New in release 1.3.0 -This is available starting at version 1.3 of **PyRandLib**: - -1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6]) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. -In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b. - -2. A short script `TestED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 1.3 this test is run on all PRNGs. -It is now **highly recommended** to not use previous releases of **PyRandLib**. - -3. Documentation has been enhanced, with typos fixed also, and erroneous docstrings have been fixed also. - -4. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. -So, if you want to see what is currently going on for next release, just check-out branch `dev`. - -5. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. - - -## Architecture overview -Each of the implemented PRNG is described in an independent module. The name of the module is directly related to the name of the related class. - - -### BaseRandom - the base class for all PRGs - -**BaseRandom** is the base class for every implemented PRNG in library -**PyRandLib**. It inherits from the Python built-in class `random.Random`. It aims at providing simple common behavior for all PRNG classes of the library, the most noticeable one being the 'callable' nature of every implemented PRNG. - -Inheriting from the Python built-in class random.Random, **BaseRandom** provides access to many useful distribution functions as described in later section **Inherited Distribution Functions**. - -Furthermore, every inheriting class may override methods: - -* random(), -* seed(), -* getrandbits(k), -* getstate() and -* setstate(). - -This lets inheriting classes implement the PRNs related core methods. - -Notice: starting at PyRandLib 1.2.0, a new signature is available with this base class. See previous section 'New in release 1.2' for full explanations. - - -### 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 with m = 2^32. - -Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use such pseudo-random numbers generators for serious simulation applications. - -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 model evaluate pseudo-random numbers suites *x(i)* as a simple mathematical function of *x(i-1)*: - - x(i) = ( a * x(i-1) + c ) mod m - -The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) since these two values have evaluated to be the *best* ones for LCGs within TestU01 while *m* = 2^63. - -Results are nevertheless considered to be poor as stated in the evaluation -done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests -than does FastRandom32. - -See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% -lower computation time. - - - -### 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 specific 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^1,457 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^49,507 periodicity - -**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with the longer period of all of the PRGs that are implemented in **PyRandLib** (2^49,507, i.e. 1.2e+14,903) with low computation time also (same as for MRGRand287) but use of much more memory space (1,597 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 PRNG 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 PRNG 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^1,340 periodicity - -**LFibRand1340** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG 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 '+'. - - - -### Well512a - 2^512 periodicity - -**Well512a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. - -It offers a long period of value 2^252 - i.e. 1.34e+154 - with short computation time and 16 integers memory consumption. -It escapes the zeroland at a fast pace. -Meanwhile, it should not be able to pass some of the *crush* and *big-crush* tests of TestU01 - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. - - - -### Well1024a - 2^1,024 periodicity - -**Well1024a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. - -It offers a long period of value 2^1024 - i.e. 2.68+308 - with short computation time and 32 integers memory consumption. -It escapes the zeroland at a fast pace. -Meanwhile, it does not pass 4 of the *crush* and 4 of the *big-crush* tests of TestU01. - - - -### Well199937b - 2^19,937 periodicity - -**Well199937b** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. - -It offers a long period of value 2^19,937 - i.e. 4.32e+6,001 - with short computation time and 624 integers memory consumption - just s the Mersenne-Twister algorithm). -It escapes the zeroland at a very fast pace. -Meanwhile, it does not pass 2 of the *crush* and 2 of the *big-crush* tests of TestU01. - - - -### Well44497c - 2^44,497 periodicity - -**WellWell44497c** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. - -It offers a long period of value 2^44,497 - i.e. 1.51e+13,466 - with short computation time and 1.391 integers memory consumption. -It escapes the zeroland at a fast pace. -Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush* tests of TestU01, while it can be expected to better behave than the Well19937b version - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. - - - -## Inherited Distribution and Generic Functions -(some of next explanation may be free to exact copy of Python 3.6 documentation. See [https://docs.python.org/3.6/library/random.html?highlight=random#module-random](https://docs.python.org/3.6/library/random.html?highlight=random#module-random)) - -Since the base class **BaseRandom** inherits from the built-in class random.Random, every PRNG class of **PyRandLib** gets automatic access to -the next distribution and generic methods: - - -**betavariate**(self, alpha, beta) - -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. - -see [http://en.wikipedia.org/wiki/Triangular_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*. -In ACM Transaction on Mathematical Software, Vol.33 N.4, Article 22 (August 2007), 40 pages. -DOI: http://dx.doi.org/10.1145/1268776.1268777 -BibTex: -@article{L'Ecuyer:2007:TCL:1268776.1268777, - author = {L'Ecuyer, Pierre and Simard, Richard}, - 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. - - -**[6]** François PANNETON and Pierre L’ECUYER (Université de Montréal) and MAKOTO MATSUMOTO (Hiroshima University). 2006. -*Improved Long-Period Generators Based on Linear Recurrences Modulo 2*. -In ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1–16. -(see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). From 7785f7c89514c56e8686bfd78602199a0ca4baa1 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 12:45:28 +0100 Subject: [PATCH 02/12] #87-implement class BaseCWG temporary commit --- PyRandLib/basecwg.py | 102 +++++++++++++++ README.md | 306 ++++++++++++++++++++----------------------- 2 files changed, 247 insertions(+), 161 deletions(-) create mode 100644 PyRandLib/basecwg.py diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py new file mode 100644 index 0000000..6692677 --- /dev/null +++ b/PyRandLib/basecwg.py @@ -0,0 +1,102 @@ +""" +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 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. (extract from [8], see README.md) + + 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 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. + """ + + #------------------------------------------------------------------------- + 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 baselcg.py ======================================== diff --git a/README.md b/README.md index 4cf49cf..c420b98 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ Many best in class pseudo random generators grouped into one simple library. ## License -PyRandLib is distributed under the MIT license for its largest use. -If you decide to use this library, please add the copyright notice to your software as stated in the LICENSE file. +PyRandLib is distributed under the MIT license for its largest use. +If you decide to use this library, please add the copyright notice to your +software as stated in the LICENSE file. ``` Copyright (c) 2016-2025 Philippe Schmouker, @@ -31,6 +32,7 @@ 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. @@ -60,28 +62,24 @@ In [1], every known PRNG at the time of the editing has been tested according to * **_crush_** is a bigger set of tests that test more deeply expected random characteristics; * **_big crush_** is the ultimate set of difficult tests that any **good** PRNG should definitively pass. -We give you here below a copy of the resulting table for the PRNGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. -We add in this table the evaluations provided by the authors of every new PRNGs that have been described after the publication of [1]. Fields may be missing then for them. A comparison of the computation times for all implemented PRNGs in **PyRandLib** is provided in an another belowing table. - - | PyRabndLib class | TU01 generator name | 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 | - | 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^1,340 | n.a. | 0.9 | 0 | 0 | 0 | - | 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^1,457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49,507 | n.a. | 1.4 | 0 | 0 | 0 | - | Pcg64_32 | not available | 2 x 4 bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Pcg128_64 | not available | 4 x 4 bytes | 2^128 | n.a. | n.a. | 0 | 0 | 0 | - | Pcg1024_32 | not available | 1,026 x 4 bytes | 2^32,830 | n.a. | n.a. | 0 | 0 | 0 | - | 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^1,024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19,937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497c | not available | 1,391 x 4-bytes | 2^44,497 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19,937 | 4.30 | 1.6 | 0 | 2 | 2 | +We give you here below a copy of the resulting table for the PRGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. + + | 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 | + | 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 | + | 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 | + | 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. | + | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19937 | 4.30 | 1.6 | 0 | 2 | 2 | (1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. @@ -100,31 +98,29 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme **PyRandLib** time-64 bits: | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | - | FastRand32 | 0.20 | | | | | 11 | 106 | *too many* | - | FastRand63 | 0.22 | | | | | 0 | 5 | 7 | - | LFib78 | 0.37 | | | | | 0 | 0 | 0 | - | LFib116 | 0.39 | | | | | 0 | 0 | 0 | - | LFib668 | 0.40 | | | | | 0 | 0 | 0 | - | LFib1340 | 0.41 | | | | | 0 | 0 | 0 | - | MRGRand287 | 0.60 | | | | | 0 | 0 | 0 | - | MRGRand1457 | 0.61 | | | | | 0 | 0 | 0 | - | MRGRand49507 | 0.58 | | | | | 0 | 0 | 0 | - | Pcg64_32 | 0.41 | | | | | 0 | 0 | 0 | - | Pcg128_64 | 0.59 | | | | | 0 | 0 | 0 | - | Pcg1024_32 | 0.82 | | | | | 0 | 0 | 0 | - | Well512a | 1.95 | | | | | n.a. | n.a. | n.a. | - | Well1024a | 1.80 | | | | | 0 | 4 | 4 | - | Well19937b (1) | 2.44 | | | | | 0 | 2 | 2 | - | Well44497c | 2.82 | | | | | n.a. | n.a. | n.a. | + | FastRand32 | 0.91 | | | | | 11 | 106 | *too many* | + | FastRand63 | 0.97 | | | | | 0 | 5 | 7 | + | LFib78 | 1.08 | | | | | 0 | 0 | 0 | + | LFib116 | 1.10 | | | | | 0 | 0 | 0 | + | LFib668 | 1.12 | | | | | 0 | 0 | 0 | + | LFib1340 | 1.12 | | | | | 0 | 0 | 0 | + | MRGRand287 | 1.41 | | | | | 0 | 0 | 0 | + | MRGRand1457 | 1.13 | | | | | 0 | 0 | 0 | + | MRGRand49507 | 1.30 | | | | | 0 | 0 | 0 | + | Well512a | 2.79 | | | | | n.a. | n.a. | n.a. | + | Well1024a | 2.60 | | | | | 0 | 4 | 4 | + | Well19937b (1) | 3.25 | | | | | 0 | 2 | 2 | + | Well44497c | 3.69 | | | | | n.a. | n.a. | n.a. | -(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. -(*missing values in empty columns are to come*) + (*missing values in empty columns are to come*) + ## Implementation Current implementation of **PyRandLib** uses Python 3.x with no Cython version. -It has been initally tested with Python 3.8 but should run with all subversions of Python 3 since 3.6. +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 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. @@ -166,54 +162,45 @@ The call operator (i.e., '()') gets a new signature which is still backward com print( rand( (5, 50.0, 500.0, 5000, [5]*8), times=3 ) ) -## New in release 2.0 -Version 2.0 of **PyRandLib** implements some new other "recent" PRNGs - see them listed below. It also provides two test scripts, enhanced documentation and some other internal development features: +## New in release 1.3.0 +This is available starting at version 1.3 of **PyRandLib**: -1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6], 2006) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (up to 1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. +1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6]) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b. -1. The PCG (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and clearly explained in technical report [7]. It offers jumping, hard to discover internal state and multi-streams featured. It passes all crush and big crush tests of TestU01. -**PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). - -1. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. -It is now **highly recommended** to not use previous releases (aka. 1.x) of **PyRandLib**. - -1. Another short script `testCPUPerfs.py` is now avaliable for testing CPU performance of the different implemented algorithms. It has been used to enhance this documentation by providing a new *times evaluation* table. +2. A short script `TestED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 1.3 this test is run on all PRNGs. +It is now **highly recommended** to not use previous releases of **PyRandLib**. -1. Documentation has been enhanced, with typos and erroneous docstrings fixed also. +3. Documentation has been enhanced, with typos fixed also, and erroneous docstrings have been fixed also. -1. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. +4. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. So, if you want to see what is currently going on for next release, just check-out branch `dev`. -1. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. +5. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. ## Architecture overview Each of the implemented PRNG is described in an independent module. The name of the module is directly related to the name of the related class. -### BaseRandom - the base class for all PRNGs +### BaseRandom - the base class for all PRGs -**BaseRandom** is the base class for every implemented PRNG in library **PyRandLib**. It inherits from the Python built-in class `random.Random`. It aims at providing simple common behavior for all PRNG classes of the library, the most noticeable one being the 'callable' nature of every implemented PRNG. +**BaseRandom** is the base class for every implemented PRNG in library +**PyRandLib**. It inherits from the Python built-in class `random.Random`. It aims at providing simple common behavior for all PRNG classes of the library, the most noticeable one being the 'callable' nature of every implemented PRNG. Inheriting from the Python built-in class random.Random, **BaseRandom** provides access to many useful distribution functions as described in later section **Inherited Distribution Functions**. -Furthermore, every inheriting class MUST override the next three methods (if not, they each raise a `NotImplementedError` exception when called): - -* next(), -* getstate() and -* setstate() - -and may override the next three methods: +Furthermore, every inheriting class may override methods: * random(), * seed(), -* getrandbits(), +* getrandbits(k), +* getstate() and +* setstate(). -Notice: starting at PyRandLib 1.2.0, a new signature is available with this base class. See previous section 'New in release 1.2' for full explanations. +This lets inheriting classes implement the PRNs related core methods. -Notice: Since PyRandLib 2.0, class `BaseRandom` implements the new method `next()` which is substituted to `random()`. `next()` should now contains the only core of the pseudo-random numbers generator while `random()` calls it to return a float value in the interval [0.0, 1.0) just as previous versions of the library. -Since version 2.0 of PyRandLib also, the newly implemented method `getrandbits()` overrides the same method of Python built-in base class `random.Random`. +Notice: starting at PyRandLib 1.2.0, a new signature is available with this base class. See previous section 'New in release 1.2' for full explanations. ### FastRand32 - 2^32 periodicity @@ -245,16 +232,29 @@ LCG model evaluate pseudo-random numbers suites *x(i)* as a simple mathematical The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) since these two values have evaluated to be the *best* ones for LCGs within TestU01 while *m* = 2^63. -Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests than does FastRandom32. +Results are nevertheless considered to be poor as stated in the evaluation +done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests +than does FastRandom32. See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% lower computation time. -### LFibRand78 - 2^78 periodicity +### 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 specific MRG 32-bits model is finally based on a Lagged Fibonacci generator (LFIB), the Marsa-LFIB4 one. -**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 @@ -264,69 +264,41 @@ where op is an operation that can be - (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 32-bits coded). - -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator -'+'. We've implemented in **PyRandLib** the original operator '+'. - - - -### LFibRand116 - 2^116 periodicity - -**LFibRand116** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG 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 32-bits coded). - -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. - +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: -### LFibRand668 - 2^668 periodicity - -**LFibRand668** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG 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 32-bits coded). - -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. - + x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 -### LFibRand1340 - 2^1,340 periodicity -**LFibRand1340** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence +### MRGRand1457 - 2^1,457 periodicity - 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 32-bits coded). +**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: -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 '+'. + x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) -### MRGRand287 - 2^287 periodicity +### MRGRand49507 - 2^49,507 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 32-bits coded memory consumption. +**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with the longer period of all of the PRGs that are implemented in **PyRandLib** (2^49,507, i.e. 1.2e+14,903) with low computation time also (same as for MRGRand287) but use of much more memory space (1,597 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: -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) = (-2^25-2^7) * ( x(i-7) + x(i-1597) ) mod (2^31-1) - 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 specific MRG 32-bits model is finally based on a Lagged Fibonacci generator (LFIB), the Marsa-LFIB4 one. +### 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 @@ -336,59 +308,57 @@ where op is an operation that can be - (substraction), * (multiplication), ^(bitwise exclusive-or). - -With the + or - operation, such generators are true MRGs. They offer very large periods with the best known results in the evaluation of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in their paper. - -The Marsa-LIBF4 version, i.e. **MRGRand287** implementation, uses the recurrence: - - x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 +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: -### MRGRand1457 - 2^1,457 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 32-bits coded). - -The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random generator proposed by Deng and Lin, see [2]. The DX-47-3 version uses the recurrence: - - x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) + 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 '+'. -### MRGRand49507 - 2^49,507 periodicity -**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with the longer period of all of the PRNGs that are implemented in **PyRandLib** (2^49,507, i.e. 1.2e+14,903) with low computation time also (same as for MRGRand287) but use of much more memory space (1,597 integers 32-bits coded). - -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) +### LFibRand116 - 2^116 periodicity +**LFibRand116** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG 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). -### Pcg64_32 - 2^64 periodicity +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 '+'. -**Pcg64_32** implements a fast 64-bits state and 32-bits output Permutated Congruential Generator with a medium period (2^64, i.e. 1.84e+19) with low computation time and very small memory space consumption (2 integers 32-bits coded). -The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is hard to be reverted and predicted. -**PyRandLib** implements for ths the *PCG XSH RS 64/32 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. +### LFibRand668 - 2^668 periodicity +**LFibRand668** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence -### Pcg128_64 - 2^128 periodicity + 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). -**Pcg128_64** implements a fast 128-bits state and 64-bits output Permutated Congruential Generator with a medium period (2^128, i.e. 3.40e+38) with low computation time and very small memory space consumption (4 integers 32-bits coded). +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 '+'. -The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. -**PyRandLib** implements for ths the *PCG XSL RR 128/64 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. +### LFibRand1340 - 2^1,340 periodicity -### Pcg1024_32 - 2^32,830 periodicity +**LFibRand1340** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence -**Pcg1024_32** implements a fast 64-bits based state and 32-bits output Permutated Congruential Generator with a very large period (2^32,830, i.e. 6.53e+9882) with low computation time and large memory space consumption (1,026 integers 32-bits coded). + 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). -The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value, and an array of 32-bits independant MCG (multiplied congruential geenrators) used to create huge chaos. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. -**PyRandLib** implements for ths the *PCG XSH RS 64/32 (EXT 1024)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. +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 '+'. @@ -396,7 +366,7 @@ The underlying algorithm acts as an LCG associated with a final permutation on b **Well512a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^252 - i.e. 1.34e+154 - with short computation time and 16 integers 32-bits coded memory consumption. +It offers a long period of value 2^252 - i.e. 1.34e+154 - with short computation time and 16 integers memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it should not be able to pass some of the *crush* and *big-crush* tests of TestU01 - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. @@ -406,7 +376,7 @@ Meanwhile, it should not be able to pass some of the *crush* and *big-crush* tes **Well1024a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^1024 - i.e. 2.68+308 - with short computation time and 32 integers 32-bits coded memory consumption. +It offers a long period of value 2^1024 - i.e. 2.68+308 - with short computation time and 32 integers memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it does not pass 4 of the *crush* and 4 of the *big-crush* tests of TestU01. @@ -416,7 +386,7 @@ Meanwhile, it does not pass 4 of the *crush* and 4 of the *big-crush* tests of T **Well199937b** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^19,937 - i.e. 4.32e+6,001 - with short computation time and 624 integers 32-bits coded memory consumption - just s the Mersenne-Twister algorithm). +It offers a long period of value 2^19,937 - i.e. 4.32e+6,001 - with short computation time and 624 integers memory consumption - just s the Mersenne-Twister algorithm). It escapes the zeroland at a very fast pace. Meanwhile, it does not pass 2 of the *crush* and 2 of the *big-crush* tests of TestU01. @@ -426,7 +396,7 @@ Meanwhile, it does not pass 2 of the *crush* and 2 of the *big-crush* tests of T **WellWell44497c** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^44,497 - i.e. 1.51e+13,466 - with short computation time and 1,391 integers 32-bits coded memory consumption. +It offers a long period of value 2^44,497 - i.e. 1.51e+13,466 - with short computation time and 1.391 integers memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush* tests of TestU01, while it can be expected to better behave than the Well19937b version - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. @@ -435,7 +405,8 @@ Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush ## Inherited Distribution and Generic Functions (some of next explanation may be free to exact copy of Python 3.6 documentation. See [https://docs.python.org/3.6/library/random.html?highlight=random#module-random](https://docs.python.org/3.6/library/random.html?highlight=random#module-random)) -Since the base class **BaseRandom** inherits from the built-in class random.Random, every PRNG class of **PyRandLib** gets automatic access to the next distribution and generic methods: +Since the base class **BaseRandom** inherits from the built-in class random.Random, every PRNG class of **PyRandLib** gets automatic access to +the next distribution and generic methods: **betavariate**(self, alpha, beta) @@ -470,7 +441,9 @@ Notice: `choices` has been provided since Python 3.6. It should be implemented f 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). +`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. @@ -505,7 +478,7 @@ Returns internal state; can be passed to `setstate()` later. 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`. +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. @@ -530,7 +503,8 @@ Returns a random integer in range [a, b], including both end points. **randrange**(self, start, stop=None, step=1) -Returns a randomly selected element from range(start, stop, step). This is equivalent to `choice( range(start, stop, step) )` without building a range object. +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. @@ -543,7 +517,8 @@ Returns a new list containing elements from the population while leaving the ori 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(10_000_000), 60)`. +To choose a sample in a range of integers, use range as an argument. This is especially fast and space efficient for sampling from a large +population: `sample(range(10000000), 60)`. **seed**(self, a=None, version=2) @@ -552,7 +527,8 @@ 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. +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. @@ -634,6 +610,7 @@ BibTex: **[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 }, @@ -644,7 +621,7 @@ number = {2}, pages = {145-150}, year = {2000}, doi = {10.1080/00031305.2000.10474528}, -URL = {http://amstat.tandfonline.com/doi/abs/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} } @@ -666,7 +643,7 @@ In ACM Transactions on Modeling and Computer Simulation (TOMACS) - Special issue Vol.8 N.1, Jan. 1998, pp. 3-30. -**[6]** François Panneton and Pierre L'Ecuyer (Université de Montréal) and Makoto Matsumoto (Hiroshima University). 2006. +**[6]** François PANNETON and Pierre L’ECUYER (Université de Montréal) and MAKOTO MATSUMOTO (Hiroshima University). 2006. *Improved Long-Period Generators Based on Linear Recurrences Modulo 2*. In ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1–16. see [https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf](https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). @@ -687,3 +664,10 @@ Finally: Harvey Mudd College Computer Science Department Technical Report, HMC-C xurl = "https://www.cs.hmc.edu/tr/hmc-cs-2014-0905.pdf", } see also [https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf](https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf). + + +**[8]** Tomasz R. Dziala. 2023. +*Collatz-Weyl Generators: High Quality and High Throughput Parameterized Pseudorandom Number Generators*. +Published at arXiv, December 2023 (11 pages) +Last reference: arXiv:2312.17043v4 [cs.CE], 2 Dec 2024, +see [https://arxiv.org/abs/2312.17043](https://arxiv.org/abs/2312.17043). From 390452714b7a1552689a324d9eb5ffc4ca7bcec0 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 13:23:30 +0100 Subject: [PATCH 03/12] #87-implement class BaseCWG temporary commit. --- PyRandLib/basecwg.py | 23 ++-- README.md | 299 +++++++++++++++++++++++-------------------- 2 files changed, 176 insertions(+), 146 deletions(-) diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py index 6692677..c167562 100644 --- a/PyRandLib/basecwg.py +++ b/PyRandLib/basecwg.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,7 +34,7 @@ class BaseCWG( BaseRandom ): 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 + 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, @@ -42,13 +42,20 @@ class BaseCWG( BaseRandom ): 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. (extract 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 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. + 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. + 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 = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated diff --git a/README.md b/README.md index c420b98..cbe969a 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ Many best in class pseudo random generators grouped into one simple library. ## License -PyRandLib is distributed under the MIT license for its largest use. -If you decide to use this library, please add the copyright notice to your -software as stated in the LICENSE file. +PyRandLib is distributed under the MIT license for its largest use. +If you decide to use this library, please add the copyright notice to your software as stated in the LICENSE file. ``` Copyright (c) 2016-2025 Philippe Schmouker, @@ -32,7 +31,6 @@ SOFTWARE. - ## Intro This library implements some of the best-in-class pseudo random generators as evaluated by Pierre L'Ecuyer and Richard Simard in their famous paper "TestU01: A C library for empirical testing of random number generators" (ACM Trans. Math. Softw. Vol. 33 N.4, August 2007 - see reference [1]. The reader will take benefit reading L'Ecuyer & Simard's paper. @@ -62,24 +60,28 @@ In [1], every known PRNG at the time of the editing has been tested according to * **_crush_** is a bigger set of tests that test more deeply expected random characteristics; * **_big crush_** is the ultimate set of difficult tests that any **good** PRNG should definitively pass. -We give you here below a copy of the resulting table for the PRGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. - - | 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 | - | 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 | - | 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 | - | 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. | - | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19937 | 4.30 | 1.6 | 0 | 2 | 2 | +We give you here below a copy of the resulting table for the PRNGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. +We add in this table the evaluations provided by the authors of every new PRNGs that have been described after the publication of [1]. Fields may be missing then for them. A comparison of the computation times for all implemented PRNGs in **PyRandLib** is provided in an another belowing table. + + | PyRabndLib class | TU01 generator name | 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 | + | 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^1,340 | n.a. | 0.9 | 0 | 0 | 0 | + | 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^1,457 | n.a. | 1.4 | 0 | 0 | 0 | + | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49,507 | n.a. | 1.4 | 0 | 0 | 0 | + | Pcg64_32 | not available | 2 x 4 bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Pcg128_64 | not available | 4 x 4 bytes | 2^128 | n.a. | n.a. | 0 | 0 | 0 | + | Pcg1024_32 | not available | 1,026 x 4 bytes | 2^32,830 | n.a. | n.a. | 0 | 0 | 0 | + | 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^1,024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19,937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44,497 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19,937 | 4.30 | 1.6 | 0 | 2 | 2 | (1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. @@ -98,29 +100,31 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme **PyRandLib** time-64 bits: | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | - | FastRand32 | 0.91 | | | | | 11 | 106 | *too many* | - | FastRand63 | 0.97 | | | | | 0 | 5 | 7 | - | LFib78 | 1.08 | | | | | 0 | 0 | 0 | - | LFib116 | 1.10 | | | | | 0 | 0 | 0 | - | LFib668 | 1.12 | | | | | 0 | 0 | 0 | - | LFib1340 | 1.12 | | | | | 0 | 0 | 0 | - | MRGRand287 | 1.41 | | | | | 0 | 0 | 0 | - | MRGRand1457 | 1.13 | | | | | 0 | 0 | 0 | - | MRGRand49507 | 1.30 | | | | | 0 | 0 | 0 | - | Well512a | 2.79 | | | | | n.a. | n.a. | n.a. | - | Well1024a | 2.60 | | | | | 0 | 4 | 4 | - | Well19937b (1) | 3.25 | | | | | 0 | 2 | 2 | - | Well44497c | 3.69 | | | | | n.a. | n.a. | n.a. | + | FastRand32 | 0.20 | | | | | 11 | 106 | *too many* | + | FastRand63 | 0.22 | | | | | 0 | 5 | 7 | + | LFib78 | 0.37 | | | | | 0 | 0 | 0 | + | LFib116 | 0.39 | | | | | 0 | 0 | 0 | + | LFib668 | 0.40 | | | | | 0 | 0 | 0 | + | LFib1340 | 0.41 | | | | | 0 | 0 | 0 | + | MRGRand287 | 0.60 | | | | | 0 | 0 | 0 | + | MRGRand1457 | 0.61 | | | | | 0 | 0 | 0 | + | MRGRand49507 | 0.58 | | | | | 0 | 0 | 0 | + | Pcg64_32 | 0.41 | | | | | 0 | 0 | 0 | + | Pcg128_64 | 0.59 | | | | | 0 | 0 | 0 | + | Pcg1024_32 | 0.82 | | | | | 0 | 0 | 0 | + | Well512a | 1.95 | | | | | n.a. | n.a. | n.a. | + | Well1024a | 1.80 | | | | | 0 | 4 | 4 | + | Well19937b (1) | 2.44 | | | | | 0 | 2 | 2 | + | Well44497c | 2.82 | | | | | n.a. | n.a. | n.a. | - (*missing values in empty columns are to come*) - +(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. +(*missing values in empty columns are to come*) ## Implementation Current implementation of **PyRandLib** uses Python 3.x with no Cython version. -It has been tested with Python 3.8 but should run with all of Python 3. +It has been initally tested with Python 3.8 but should run with all subversions of Python 3 since 3.6. -Note 1: **PyRandLib** version 1.1 and below should work with all versions of Python 3. In version 1.2, we have added underscores in numerical constants -for the better readability of the code. This feature has been introduced in Python 3.6. If you want to use PyRandLib version 1.2 or above with Python 3.5 or below, removing these underscores should be sufficient to have the library running correctly. +Note 1: **PyRandLib** version 1.1 and below should work with all versions of Python 3. In version 1.2, we have added underscores in numerical constants for the better readability of the code. This feature has been introduced in Python 3.6. If you want to use PyRandLib version 1.2 or above with Python 3.5 or below, removing these underscores should be sufficient to have the library running correctly. Note 2: no version or **PyRandLib** will ever be provided for Python 2 which is a no more maintained version of the Python language. @@ -162,45 +166,54 @@ The call operator (i.e., '()') gets a new signature which is still backward com print( rand( (5, 50.0, 500.0, 5000, [5]*8), times=3 ) ) -## New in release 1.3.0 -This is available starting at version 1.3 of **PyRandLib**: +## New in release 2.0 +Version 2.0 of **PyRandLib** implements some new other "recent" PRNGs - see them listed below. It also provides two test scripts, enhanced documentation and some other internal development features: -1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6]) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. +1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6], 2006) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (up to 1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b. -2. A short script `TestED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 1.3 this test is run on all PRNGs. -It is now **highly recommended** to not use previous releases of **PyRandLib**. +1. The PCG (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and clearly explained in technical report [7]. It offers jumping, hard to discover internal state and multi-streams featured. It passes all crush and big crush tests of TestU01. +**PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). + +1. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. +It is now **highly recommended** to not use previous releases (aka. 1.x) of **PyRandLib**. + +1. Another short script `testCPUPerfs.py` is now avaliable for testing CPU performance of the different implemented algorithms. It has been used to enhance this documentation by providing a new *times evaluation* table. -3. Documentation has been enhanced, with typos fixed also, and erroneous docstrings have been fixed also. +1. Documentation has been enhanced, with typos and erroneous docstrings fixed also. -4. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. +1. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. So, if you want to see what is currently going on for next release, just check-out branch `dev`. -5. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. +1. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. ## Architecture overview Each of the implemented PRNG is described in an independent module. The name of the module is directly related to the name of the related class. -### BaseRandom - the base class for all PRGs +### BaseRandom - the base class for all PRNGs -**BaseRandom** is the base class for every implemented PRNG in library -**PyRandLib**. It inherits from the Python built-in class `random.Random`. It aims at providing simple common behavior for all PRNG classes of the library, the most noticeable one being the 'callable' nature of every implemented PRNG. +**BaseRandom** is the base class for every implemented PRNG in library **PyRandLib**. It inherits from the Python built-in class `random.Random`. It aims at providing simple common behavior for all PRNG classes of the library, the most noticeable one being the 'callable' nature of every implemented PRNG. Inheriting from the Python built-in class random.Random, **BaseRandom** provides access to many useful distribution functions as described in later section **Inherited Distribution Functions**. -Furthermore, every inheriting class may override methods: +Furthermore, every inheriting class MUST override the next three methods (if not, they each raise a `NotImplementedError` exception when called): + +* next(), +* getstate() and +* setstate() + +and may override the next three methods: * random(), * seed(), -* getrandbits(k), -* getstate() and -* setstate(). +* getrandbits(), -This lets inheriting classes implement the PRNs related core methods. +Notice: starting at PyRandLib 1.2.0, a new signature is available with this base class. See previous section 'New in release 1.2' for full explanations. -Notice: starting at PyRandLib 1.2.0, a new signature is available with this base class. See previous section 'New in release 1.2' for full explanations. +Notice: Since PyRandLib 2.0, class `BaseRandom` implements the new method `next()` which is substituted to `random()`. `next()` should now contains the only core of the pseudo-random numbers generator while `random()` calls it to return a float value in the interval [0.0, 1.0) just as previous versions of the library. +Since version 2.0 of PyRandLib also, the newly implemented method `getrandbits()` overrides the same method of Python built-in base class `random.Random`. ### FastRand32 - 2^32 periodicity @@ -232,29 +245,16 @@ LCG model evaluate pseudo-random numbers suites *x(i)* as a simple mathematical The implementation of this LCG 63-bits model is based on (*a*=9219741426499971445, *c*=1) since these two values have evaluated to be the *best* ones for LCGs within TestU01 while *m* = 2^63. -Results are nevertheless considered to be poor as stated in the evaluation -done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests -than does FastRandom32. +Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests than does FastRandom32. See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% lower computation time. -### 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 specific MRG 32-bits model is finally based on a Lagged Fibonacci generator (LFIB), the Marsa-LFIB4 one. +### 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 @@ -264,41 +264,69 @@ where op is an operation that can be - (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 32-bits coded). + +Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator +'+'. We've implemented in **PyRandLib** the original operator '+'. + + + +### LFibRand116 - 2^116 periodicity + +**LFibRand116** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence + + x(i) = ( x(i-24) + x(i-55) ) mod 2^64 -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. +It offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due to the use of a 2^64 modulo (less than twice the computation time of LCGs) and some memory consumption (55 integers 32-bits coded). -The Marsa-LIBF4 version, i.e. **MRGRand287** implementation, uses the -recurrence: +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 '+'. - x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 +### LFibRand668 - 2^668 periodicity -### MRGRand1457 - 2^1,457 periodicity +**LFibRand668** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence -**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) = ( 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 32-bits coded). - x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) +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 '+'. -### MRGRand49507 - 2^49,507 periodicity +### LFibRand1340 - 2^1,340 periodicity -**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with the longer period of all of the PRGs that are implemented in **PyRandLib** (2^49,507, i.e. 1.2e+14,903) with low computation time also (same as for MRGRand287) but use of much more memory space (1,597 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: +**LFibRand1340** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence - x(i) = (-2^25-2^7) * ( x(i-7) + x(i-1597) ) mod (2^31-1) + 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 32-bits coded). +Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator '+'. We've implemented in **PyRandLib** the original operator '+'. -### LFibRand78 - 2^78 periodicity -**LFibRand78** implements a fast 64-bits Lagged Fibonacci generator (LFib). +### 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 32-bits coded 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 specific 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 @@ -308,57 +336,59 @@ where op is an operation that can be - (substraction), * (multiplication), ^(bitwise exclusive-or). + +With the + or - operation, such generators are true MRGs. They offer very large periods with the best known results in the evaluation of their randomness, as evaluated by Pierre L'Ecuyer and Richard Simard in their paper. -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 Marsa-LIBF4 version, i.e. **MRGRand287** implementation, uses the recurrence: -The implementation of **LFibRand78** is based on a Lagged Fibonacci generator (LFib) which uses the recurrence: + x(i) = ( x(i-55) + x(i-119) + x(i-179) + x(i-256) ) mod 2^32 - 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 '+'. +### MRGRand1457 - 2^1,457 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 32-bits coded). + +The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random generator proposed by Deng and Lin, see [2]. The DX-47-3 version uses the recurrence: + x(i) = (2^26+2^19) * ( x(i-1) + x(i-24) + x(i-47) ) mod (2^31-1) -### LFibRand116 - 2^116 periodicity -**LFibRand116** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG 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). +### MRGRand49507 - 2^49,507 periodicity -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 '+'. +**MRGRand49507** implements a fast 31-bits Multiple Recursive Generator with the longer period of all of the PRNGs that are implemented in **PyRandLib** (2^49,507, i.e. 1.2e+14,903) with low computation time also (same as for MRGRand287) but use of much more memory space (1,597 integers 32-bits coded). + +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) -### LFibRand668 - 2^668 periodicity -**LFibRand668** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence +### Pcg64_32 - 2^64 periodicity - 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). +**Pcg64_32** implements a fast 64-bits state and 32-bits output Permutated Congruential Generator with a medium period (2^64, i.e. 1.84e+19) with low computation time and very small memory space consumption (2 integers 32-bits coded). -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 '+'. +The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is hard to be reverted and predicted. +**PyRandLib** implements for ths the *PCG XSH RS 64/32 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. -### LFibRand1340 - 2^1,340 periodicity +### Pcg128_64 - 2^128 periodicity -**LFibRand1340** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence +**Pcg128_64** implements a fast 128-bits state and 64-bits output Permutated Congruential Generator with a medium period (2^128, i.e. 3.40e+38) with low computation time and very small memory space consumption (4 integers 32-bits coded). - 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). +The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. +**PyRandLib** implements for ths the *PCG XSL RR 128/64 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. -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 '+'. + + +### Pcg1024_32 - 2^32,830 periodicity + +**Pcg1024_32** implements a fast 64-bits based state and 32-bits output Permutated Congruential Generator with a very large period (2^32,830, i.e. 6.53e+9882) with low computation time and large memory space consumption (1,026 integers 32-bits coded). + +The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value, and an array of 32-bits independant MCG (multiplied congruential geenrators) used to create huge chaos. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. +**PyRandLib** implements for ths the *PCG XSH RS 64/32 (EXT 1024)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. @@ -366,7 +396,7 @@ Please notice that the TestUO1 article states that the operator should be '*' wh **Well512a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^252 - i.e. 1.34e+154 - with short computation time and 16 integers memory consumption. +It offers a long period of value 2^252 - i.e. 1.34e+154 - with short computation time and 16 integers 32-bits coded memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it should not be able to pass some of the *crush* and *big-crush* tests of TestU01 - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. @@ -376,7 +406,7 @@ Meanwhile, it should not be able to pass some of the *crush* and *big-crush* tes **Well1024a** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^1024 - i.e. 2.68+308 - with short computation time and 32 integers memory consumption. +It offers a long period of value 2^1024 - i.e. 2.68+308 - with short computation time and 32 integers 32-bits coded memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it does not pass 4 of the *crush* and 4 of the *big-crush* tests of TestU01. @@ -386,7 +416,7 @@ Meanwhile, it does not pass 4 of the *crush* and 4 of the *big-crush* tests of T **Well199937b** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^19,937 - i.e. 4.32e+6,001 - with short computation time and 624 integers memory consumption - just s the Mersenne-Twister algorithm). +It offers a long period of value 2^19,937 - i.e. 4.32e+6,001 - with short computation time and 624 integers 32-bits coded memory consumption - just s the Mersenne-Twister algorithm). It escapes the zeroland at a very fast pace. Meanwhile, it does not pass 2 of the *crush* and 2 of the *big-crush* tests of TestU01. @@ -396,7 +426,7 @@ Meanwhile, it does not pass 2 of the *crush* and 2 of the *big-crush* tests of T **WellWell44497c** implements the Well-Equilibrated Long-period Linear generators (WELL) proposed by François Panneton, Pierre L'ECcuyer and Makoto Matsumoto in [6]. This PRNG uses linear recurrence based on primitive characteristic polynomials associated with left- and right- shifts and xor operations to fastly evaluate pseudo-random numbers suites. -It offers a long period of value 2^44,497 - i.e. 1.51e+13,466 - with short computation time and 1.391 integers memory consumption. +It offers a long period of value 2^44,497 - i.e. 1.51e+13,466 - with short computation time and 1,391 integers 32-bits coded memory consumption. It escapes the zeroland at a fast pace. Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush* tests of TestU01, while it can be expected to better behave than the Well19937b version - notice: this version of the WELL algorithm has not been tested in original TestU01 paper. @@ -405,8 +435,7 @@ Meanwhile, it might not be able to pass a very few of the *crush* and *big-crush ## Inherited Distribution and Generic Functions (some of next explanation may be free to exact copy of Python 3.6 documentation. See [https://docs.python.org/3.6/library/random.html?highlight=random#module-random](https://docs.python.org/3.6/library/random.html?highlight=random#module-random)) -Since the base class **BaseRandom** inherits from the built-in class random.Random, every PRNG class of **PyRandLib** gets automatic access to -the next distribution and generic methods: +Since the base class **BaseRandom** inherits from the built-in class random.Random, every PRNG class of **PyRandLib** gets automatic access to the next distribution and generic methods: **betavariate**(self, alpha, beta) @@ -441,9 +470,7 @@ Notice: `choices` has been provided since Python 3.6. It should be implemented f 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). +`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. @@ -478,7 +505,7 @@ Returns internal state; can be passed to `setstate()` later. 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`. +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. @@ -503,8 +530,7 @@ Returns a random integer in range [a, b], including both end points. **randrange**(self, start, stop=None, step=1) -Returns a randomly selected element from range(start, stop, step). This is -equivalent to `choice( range(start, stop, step) )` without building a range object. +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. @@ -517,8 +543,7 @@ Returns a new list containing elements from the population while leaving the ori Members of the population need not be hashable or unique. If the population contains repeats, then each occurrence is a possible selection in the sample. -To choose a sample in a range of integers, use range as an argument. This is especially fast and space efficient for sampling from a large -population: `sample(range(10000000), 60)`. +To choose a sample in a range of integers, use range as an argument. This is especially fast and space efficient for sampling from a large population: `sample(range(10_000_000), 60)`. **seed**(self, a=None, version=2) @@ -527,8 +552,7 @@ 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. +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. @@ -610,7 +634,6 @@ BibTex: **[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 }, @@ -621,7 +644,7 @@ number = {2}, pages = {145-150}, year = {2000}, doi = {10.1080/00031305.2000.10474528}, -URL = {ttp://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, +URL = {http://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, eprint = {http://amstat.tandfonline.com/doi/pdf/10.1080/00031305.2000.10474528} } @@ -643,7 +666,7 @@ In ACM Transactions on Modeling and Computer Simulation (TOMACS) - Special issue Vol.8 N.1, Jan. 1998, pp. 3-30. -**[6]** François PANNETON and Pierre L’ECUYER (Université de Montréal) and MAKOTO MATSUMOTO (Hiroshima University). 2006. +**[6]** François Panneton and Pierre L'Ecuyer (Université de Montréal) and Makoto Matsumoto (Hiroshima University). 2006. *Improved Long-Period Generators Based on Linear Recurrences Modulo 2*. In ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1–16. see [https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf](https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). From 64a8be4a230b06ae2f523bad241f28ec11d34c2f Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 16:01:41 +0100 Subject: [PATCH 04/12] #87-implement class BaseCWG Completed. Still to be tested. --- PyRandLib/annotation_types.py | 2 +- PyRandLib/basecwg.py | 31 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/PyRandLib/annotation_types.py b/PyRandLib/annotation_types.py index b1a6f8b..e926d8a 100644 --- a/PyRandLib/annotation_types.py +++ b/PyRandLib/annotation_types.py @@ -25,7 +25,7 @@ Numerical = Union[ int, float ] StatesList = Union[ Tuple[int], List[int] ] -StateType = Union[ StatesList, Tuple[StatesList, int] ] +StateType = Union[ StatesList, Tuple[StatesList, int], List[StatesList, int] ] SeedStateType = Union[ Numerical, StateType ] diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py index c167562..e36d5a2 100644 --- a/PyRandLib/basecwg.py +++ b/PyRandLib/basecwg.py @@ -22,7 +22,7 @@ #============================================================================= from .baserandom import BaseRandom -from .annotation_types import Numerical +from .annotation_types import SeedStateType, StatesListAndState #============================================================================= @@ -58,20 +58,20 @@ class BaseCWG( BaseRandom ): very good randomness characteristics. Furthermore this class is callable: - rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + 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 LCGs that have - been implemented in PyRandLib, as provided in paper "TestU01, ..." - see - file README.md. + been implemented in PyRandLib, as presented in paper [8] - 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 | + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | --------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 1 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 2 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 2 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 PRG; @@ -82,7 +82,7 @@ class BaseCWG( BaseRandom ): """ #------------------------------------------------------------------------- - def __init__(self, _seedState: Numerical = None) -> None: + def __init__(self, _seedState: SeedStateType = None) -> None: """Constructor. Should _seedState be None then the local time is used as a seed (with @@ -96,14 +96,17 @@ def __init__(self, _seedState: Numerical = None) -> None: #------------------------------------------------------------------------- - def getstate(self) -> int: + def getstate(self) -> StatesListAndState: """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. + 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._state + raise NotImplementedError() #===== end of module baselcg.py ======================================== From 0db8b9cd41b0f40b6f499d8fe323cb4bcaad2ed7 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 16:03:56 +0100 Subject: [PATCH 05/12] #45-enhance docstrings content Modified a few lines. --- PyRandLib/baselcg.py | 4 ++-- PyRandLib/baserandom.py | 8 ++++++-- PyRandLib/pcg1024_32.py | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/PyRandLib/baselcg.py b/PyRandLib/baselcg.py index 25291e3..88d7bf9 100644 --- a/PyRandLib/baselcg.py +++ b/PyRandLib/baselcg.py @@ -95,8 +95,8 @@ 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 + 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 diff --git a/PyRandLib/baserandom.py b/PyRandLib/baserandom.py index c81aca0..30a5cca 100644 --- a/PyRandLib/baserandom.py +++ b/PyRandLib/baserandom.py @@ -249,8 +249,12 @@ class BaseRandom( Random ): def __init__(self, _seed: SeedStateType = None) -> None: """Constructor. - Should _seed be None or not an integer then the local - time is used (with its shuffled value) as a seed. + 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 ) diff --git a/PyRandLib/pcg1024_32.py b/PyRandLib/pcg1024_32.py index 82a8754..880e2ae 100644 --- a/PyRandLib/pcg1024_32.py +++ b/PyRandLib/pcg1024_32.py @@ -136,6 +136,7 @@ def next(self) -> int: # 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. @@ -209,8 +210,11 @@ def _advancetable(self) -> None: #------------------------------------------------------------------------- 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 ##self._invxrs( value, 32, 22 ) + 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 From a0638b7ef23735391e50b6270ff280fa14e6fe4c Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 18:46:29 +0100 Subject: [PATCH 06/12] #88-implement class Cwg64 Completed. Validated. --- PyRandLib/__init__.py | 2 + PyRandLib/annotation_types.py | 9 +- PyRandLib/basecwg.py | 43 +++++++-- PyRandLib/cwg64.py | 172 ++++++++++++++++++++++++++++++++++ PyRandLib/pcg128_64.py | 2 +- README.md | 16 ++-- testED.py | 3 +- 7 files changed, 226 insertions(+), 21 deletions(-) create mode 100644 PyRandLib/cwg64.py diff --git a/PyRandLib/__init__.py b/PyRandLib/__init__.py index 38f0b50..ecf8412 100644 --- a/PyRandLib/__init__.py +++ b/PyRandLib/__init__.py @@ -6,11 +6,13 @@ Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com """ +from .basecwg import BaseCWG from .baselcg import BaseLCG from .baselfib64 import BaseLFib64 from .basemrg import BaseMRG from .baserandom import BaseRandom from .basewell import BaseWELL +from .cwg64 import Cwg64 from .fastrand32 import FastRand32 from .fastrand63 import FastRand63 from .lfib78 import LFib78 diff --git a/PyRandLib/annotation_types.py b/PyRandLib/annotation_types.py index e926d8a..06f3b88 100644 --- a/PyRandLib/annotation_types.py +++ b/PyRandLib/annotation_types.py @@ -23,10 +23,11 @@ #============================================================================= from typing import List, Tuple, Union -Numerical = Union[ int, float ] -StatesList = Union[ Tuple[int], List[int] ] -StateType = Union[ StatesList, Tuple[StatesList, int], List[StatesList, int] ] -SeedStateType = Union[ Numerical, StateType ] +Numerical = Union[ int, float ] +StatesList = Union[ Tuple[int], List[int] ] +StatesListAndState = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndState ] +SeedStateType = Union[ Numerical, StateType ] #===== end of PyRandLib.annotation_types =============================== diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py index e36d5a2..cfdb739 100644 --- a/PyRandLib/basecwg.py +++ b/PyRandLib/basecwg.py @@ -41,7 +41,7 @@ class BaseCWG( BaseRandom ): 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. (extract from [8], see README.md) + 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 @@ -67,11 +67,11 @@ class BaseCWG( BaseRandom ): We give you here below a copy of the table of tests for the LCGs 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 | 1 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | - | Cwg128_64 | CWG128_64 | 2 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ - | Cwg128 | CWG128 | 2 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + | 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 PRG; @@ -93,7 +93,7 @@ def __init__(self, _seedState: SeedStateType = None) -> None: """ super().__init__( _seedState ) # this internally calls 'setstate()' which # MUST be implemented in inheriting classes - + #------------------------------------------------------------------------- def getstate(self) -> StatesListAndState: @@ -108,5 +108,30 @@ def getstate(self) -> StatesListAndState: All inheriting classes MUST IMPLEMENT this method. """ raise NotImplementedError() - -#===== end of module baselcg.py ======================================== + + +#============================================================================= +class SplitMix: + """The splitting and mixing algorithm used to intiialize CWGs states. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: int) -> None: + """Constructor. + """ + self.state = _seed & 0xffff_ffff_ffff_ffff + + #------------------------------------------------------------------------- + def __call__(self, _mask: int = 0xffff_ffff_ffff_ffff) -> int: + """The shuffle algorithm. + """ + self.state += 0x9e37_79b9_7f4a_7c15 + self.state &= 0xffff_ffff_ffff_ffff + + z = self.state & _mask + z = ((z ^ (z >> 30)) * 0xbf58476d1ce4e5b9) & _mask + z = ((z ^ (z >> 27)) * 0x94d049bb133111eb) & _mask + + return z ^ (z >> 31) + + +#===== end of module basecwg.py ======================================== diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py new file mode 100644 index 0000000..bedaf0b --- /dev/null +++ b/PyRandLib/cwg64.py @@ -0,0 +1,172 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF 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, SplitMix +from .fastrand32 import FastRand32 +from .annotation_types import SeedStateType + + +#============================================================================= +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 very 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 LCGs 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 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. + """ + + + #------------------------------------------------------------------------- + _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._state + self._weyl += self._s + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + """ + return (self._a, self._weyl, self._s, self._state) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """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 + splitMix = SplitMix( _state ) + self._a = self._weyl = 0 + self._state = splitMix(); + self._s = (splitMix(0x7fff_ffff_ffff_ffff) << 1) | 1; + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self.setstate( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self.setstate( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + 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 + init_rand = FastRand32() + self.setstate( init_rand.next() | (init_rand.next() << 32) ) + + +#===== end of module baselcg.py ======================================== diff --git a/PyRandLib/pcg128_64.py b/PyRandLib/pcg128_64.py index 6a81cf3..e62d9f0 100644 --- a/PyRandLib/pcg128_64.py +++ b/PyRandLib/pcg128_64.py @@ -161,7 +161,7 @@ def setstate(self, _state: Numerical) -> None: if _state < 0.0 : _state = -_state if _state >= 1.0: - self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffffffff_ffff_ffff_ffff + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff else: self._state = int( _state * 0x1_0000_0000_0000_0000_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff diff --git a/README.md b/README.md index cbe969a..5a24d2e 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,11 @@ In [1], every known PRNG at the time of the editing has been tested according to We give you here below a copy of the resulting table for the PRNGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. We add in this table the evaluations provided by the authors of every new PRNGs that have been described after the publication of [1]. Fields may be missing then for them. A comparison of the computation times for all implemented PRNGs in **PyRandLib** is provided in an another belowing table. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | PyRabndLib class | TU01 generator name (1) | 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 | | 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 | | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | @@ -74,16 +77,17 @@ We add in this table the evaluations provided by the authors of every new PRNGs | 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^1,457 | n.a. | 1.4 | 0 | 0 | 0 | | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49,507 | n.a. | 1.4 | 0 | 0 | 0 | - | Pcg64_32 | not available | 2 x 4 bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | - | Pcg128_64 | not available | 4 x 4 bytes | 2^128 | n.a. | n.a. | 0 | 0 | 0 | - | Pcg1024_32 | not available | 1,026 x 4 bytes | 2^32,830 | n.a. | n.a. | 0 | 0 | 0 | + | Pcg64_32 | *PCG XSH RS 64/32 (LCG)* | 2 x 4 bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Pcg128_64 | *PCG XSL RR 128/64 (LCG)* | 4 x 4 bytes | 2^128 | n.a. | n.a. | 0 | 0 | 0 | + | Pcg1024_32 | *PCG XSH RS 64/32 (EXT 1024)* | 1,026 x 4 bytes | 2^32,830 | n.a. | n.a. | 0 | 0 | 0 | | 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^1,024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19,937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well19937b (2) | WELL19937a | 624 x 4-bytes | 2^19,937 | 4.3 | 1.3 | 0 | 2 | 2 | | Well44497c | not available | 1,391 x 4-bytes | 2^44,497 | n.a. | n.a. | n.a. | n.a. | n.a. | | Mersenne twister | MT19937 | 6 x 4-bytes | 2^19,937 | 4.30 | 1.6 | 0 | 2 | 2 | -(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. +(1)*or generator original name in related paper* +(2)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. diff --git a/testED.py b/testED.py index cb458a9..124a73c 100644 --- a/testED.py +++ b/testED.py @@ -108,7 +108,8 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): #============================================================================= if __name__ == "__main__": - test_algo(FastRand32(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(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) From f95ff4c255baea9997e242531d906289c33d64a8 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 19:49:37 +0100 Subject: [PATCH 07/12] #89-implement class Cwg128_64 Completed. Validated. --- PyRandLib/__init__.py | 1 + PyRandLib/basecwg.py | 4 +- PyRandLib/cwg128_64.py | 172 +++++++++++++++++++++++++++++++++++++++++ PyRandLib/cwg64.py | 14 ++-- README.md | 26 ++++--- testCPUPerfs.py | 34 ++++---- testED.py | 36 +++++---- 7 files changed, 233 insertions(+), 54 deletions(-) create mode 100644 PyRandLib/cwg128_64.py diff --git a/PyRandLib/__init__.py b/PyRandLib/__init__.py index ecf8412..6c8052a 100644 --- a/PyRandLib/__init__.py +++ b/PyRandLib/__init__.py @@ -13,6 +13,7 @@ from .baserandom import BaseRandom from .basewell import BaseWELL from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 from .fastrand32 import FastRand32 from .fastrand63 import FastRand63 from .lfib78 import LFib78 diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py index cfdb739..339a78e 100644 --- a/PyRandLib/basecwg.py +++ b/PyRandLib/basecwg.py @@ -48,8 +48,8 @@ class BaseCWG( BaseRandom ): description. 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. + 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. diff --git a/PyRandLib/cwg128_64.py b/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..294b4be --- /dev/null +++ b/PyRandLib/cwg128_64.py @@ -0,0 +1,172 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF 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, SplitMix +from .fastrand32 import FastRand32 +from .annotation_types import SeedStateType + + +#============================================================================= +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 LCGs 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 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. + """ + + + #------------------------------------------------------------------------- + _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 getstate(self) -> Tuple[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + """ + return (self._a, self._weyl, self._s, self._state) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """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 + splitMix = SplitMix( _state ) + self._a = self._weyl = 0 + self._state = (splitMix() << 64) | splitMix() + self._s = (splitMix(0x7fff_ffff_ffff_ffff) << 1) | 1; + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self.setstate( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self.setstate( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + 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_ffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + init_rand = FastRand32() + self.setstate( init_rand.next() | (init_rand.next() << 32) | (init_rand.next() << 64) | (init_rand.next() << 96) ) + + +#===== end of module cwg64.py ========================================== diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py index bedaf0b..2a0cacd 100644 --- a/PyRandLib/cwg64.py +++ b/PyRandLib/cwg64.py @@ -33,8 +33,8 @@ 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 very short computation time. All - CWG algorithms offer multi streams features, by simply using different + 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. @@ -115,8 +115,8 @@ def next(self) -> int: """This is the core of the pseudo-random generator. """ # evaluates next internal state - self._a += self._state - self._weyl += self._s + 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) @@ -144,8 +144,8 @@ def setstate(self, _state: SeedStateType) -> None: # passed initial seed is an integer, just uses it splitMix = SplitMix( _state ) self._a = self._weyl = 0 - self._state = splitMix(); - self._s = (splitMix(0x7fff_ffff_ffff_ffff) << 1) | 1; + self._state = splitMix() + self._s = (splitMix(0x7fff_ffff_ffff_ffff) << 1) | 1 elif isinstance( _state, float ): # transforms passed initial seed from float to integer @@ -169,4 +169,4 @@ def setstate(self, _state: SeedStateType) -> None: self.setstate( init_rand.next() | (init_rand.next() << 32) ) -#===== end of module baselcg.py ======================================== +#===== end of module cwg64.py ========================================== diff --git a/README.md b/README.md index 5a24d2e..0f079d4 100644 --- a/README.md +++ b/README.md @@ -104,21 +104,23 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme **PyRandLib** time-64 bits: | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | + | Cwg64 | 0.60 | | | | | 0 | 0 | 0 | + | Cwg128_64_ | 0.60 | | | | | 0 | 0 | 0 | | FastRand32 | 0.20 | | | | | 11 | 106 | *too many* | - | FastRand63 | 0.22 | | | | | 0 | 5 | 7 | - | LFib78 | 0.37 | | | | | 0 | 0 | 0 | - | LFib116 | 0.39 | | | | | 0 | 0 | 0 | - | LFib668 | 0.40 | | | | | 0 | 0 | 0 | - | LFib1340 | 0.41 | | | | | 0 | 0 | 0 | - | MRGRand287 | 0.60 | | | | | 0 | 0 | 0 | - | MRGRand1457 | 0.61 | | | | | 0 | 0 | 0 | - | MRGRand49507 | 0.58 | | | | | 0 | 0 | 0 | - | Pcg64_32 | 0.41 | | | | | 0 | 0 | 0 | - | Pcg128_64 | 0.59 | | | | | 0 | 0 | 0 | - | Pcg1024_32 | 0.82 | | | | | 0 | 0 | 0 | + | FastRand63 | 0.21 | | | | | 0 | 5 | 7 | + | LFib78 | 0.35 | | | | | 0 | 0 | 0 | + | LFib116 | 0.35 | | | | | 0 | 0 | 0 | + | LFib668 | 0.37 | | | | | 0 | 0 | 0 | + | LFib1340 | 0.39 | | | | | 0 | 0 | 0 | + | MRGRand287 | 0.57 | | | | | 0 | 0 | 0 | + | MRGRand1457 | 0.58 | | | | | 0 | 0 | 0 | + | MRGRand49507 | 0.54 | | | | | 0 | 0 | 0 | + | Pcg64_32 | 0.39 | | | | | 0 | 0 | 0 | + | Pcg128_64 | 0.57 | | | | | 0 | 0 | 0 | + | Pcg1024_32 | 0.80 | | | | | 0 | 0 | 0 | | Well512a | 1.95 | | | | | n.a. | n.a. | n.a. | | Well1024a | 1.80 | | | | | 0 | 4 | 4 | - | Well19937b (1) | 2.44 | | | | | 0 | 2 | 2 | + | Well19937b (1) | 2.43 | | | | | 0 | 2 | 2 | | Well44497c | 2.82 | | | | | n.a. | n.a. | n.a. | (1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. diff --git a/testCPUPerfs.py b/testCPUPerfs.py index 8420e67..b3ce2f6 100644 --- a/testCPUPerfs.py +++ b/testCPUPerfs.py @@ -49,22 +49,24 @@ def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: in N = 15 - test_perf("FastRand32" , 0x3ca5_8796 , 2_000_000, N) - test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("MRGRand287" , 0x3ca5_8796 , 2_000_000, N) - test_perf("MRGRand1457" , 0x3ca5_8796 , 2_000_000, N) - test_perf("MRGRand49507", 0x3ca5_8796 , 2_000_000, N) - test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 2_000_000, N) - test_perf("Well512a" , 0x3ca5_8796 , 1_000_000, N) - test_perf("Well1024a" , 0x3ca5_8796 , 1_000_000, N) - test_perf("Well19937c" , 0x3ca5_8796 , 1_000_000, N) - test_perf("Well44497b" , 0x3ca5_8796 , 1_000_000, N) + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_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("MRGRand287" , 0x3ca5_8796 , 100_000, N) + test_perf("MRGRand1457" , 0x3ca5_8796 , 100_000, N) + test_perf("MRGRand49507", 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("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) #===== end of module testCPUPerfs.py =================================== diff --git a/testED.py b/testED.py index 124a73c..6955a3f 100644 --- a/testED.py +++ b/testED.py @@ -99,7 +99,8 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): 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}]") + 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.") @@ -109,22 +110,23 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): #============================================================================= if __name__ == "__main__": test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number - test_algo(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(MRGRand287(), 3217, nb_loops = 2_000_000) - test_algo(MRGRand1457(), 3217, nb_loops = 2_000_000) - test_algo(MRGRand49507(), 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(Well512a(), 3217, nb_loops = 1_500_000) - test_algo(Well1024a(), 3217, nb_loops = 1_500_000) - test_algo(Well19937c(), nb_entries = 2029) # notice: 2029 is a prime number - test_algo(Well44497b(), nb_entries = 2029) + test_algo(Cwg128_64(), 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(MRGRand287(), 3217, nb_loops = 2_000_000) + #test_algo(MRGRand1457(), 3217, nb_loops = 2_000_000) + #test_algo(MRGRand49507(), 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(Well512a(), 3217, nb_loops = 1_500_000) + #test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + #test_algo(Well19937c(), nb_entries = 2029) # notice: 2029 is a prime number + #test_algo(Well44497b(), nb_entries = 2029) #===== end of module testED.py ========================================= From 01a70c74b3a742b276380a9efcf4835f9afe23f4 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Thu, 27 Feb 2025 23:47:48 +0100 Subject: [PATCH 08/12] #90-implement class Cwg128 Completed. Validated. --- PyRandLib/__init__.py | 1 + PyRandLib/cwg128.py | 175 +++++++++++++++++++++++++++++++++++++++++ PyRandLib/cwg128_64.py | 2 +- PyRandLib/cwg64.py | 2 +- README.md | 1 + testCPUPerfs.py | 33 ++++---- testED.py | 1 + 7 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 PyRandLib/cwg128.py diff --git a/PyRandLib/__init__.py b/PyRandLib/__init__.py index 6c8052a..4e2d8a6 100644 --- a/PyRandLib/__init__.py +++ b/PyRandLib/__init__.py @@ -14,6 +14,7 @@ from .basewell import BaseWELL 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 diff --git a/PyRandLib/cwg128.py b/PyRandLib/cwg128.py new file mode 100644 index 0000000..76b59f6 --- /dev/null +++ b/PyRandLib/cwg128.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG, SplitMix +from .fastrand32 import FastRand32 +from .annotation_types import SeedStateType + + +#============================================================================= +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 LCGs 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 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. + """ + + + #------------------------------------------------------------------------- + _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. + """ + + + #------------------------------------------------------------------------- + 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_ffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff_ffff_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 >> 96) + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + """ + return (self._a, self._weyl, self._s, self._state) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """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 + splitMix = SplitMix( _state ) + self._a = self._weyl = 0 + self._state = (splitMix() << 64) | splitMix() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (splitMix() << 64) | (splitMix(0x7fff_ffff_ffff_ffff) << 1) | 1 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self.setstate( int(_state + 0.5) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff ) + else: + self.setstate( int(_state * 0x1_0000_0000_0000_0000_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff ) + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + init_rand = FastRand32() + self.setstate( init_rand.next() | (init_rand.next() << 32) | (init_rand.next() << 64) | (init_rand.next() << 96) ) + +#===== end of module cwg128.py ========================================= diff --git a/PyRandLib/cwg128_64.py b/PyRandLib/cwg128_64.py index 294b4be..c23489e 100644 --- a/PyRandLib/cwg128_64.py +++ b/PyRandLib/cwg128_64.py @@ -60,7 +60,7 @@ class Cwg128_64( BaseCWG ): very good randomness characteristics. Furthermore this class is callable: - rand = CWG128_64() + 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) diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py index 2a0cacd..b154c10 100644 --- a/PyRandLib/cwg64.py +++ b/PyRandLib/cwg64.py @@ -60,7 +60,7 @@ class Cwg64( BaseCWG ): very good randomness characteristics. Furthermore this class is callable: - rand = CWG64() + 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) diff --git a/README.md b/README.md index 0f079d4..0594944 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | | Cwg64 | 0.60 | | | | | 0 | 0 | 0 | | Cwg128_64_ | 0.60 | | | | | 0 | 0 | 0 | + | Cwg128 | 0.63 | | | | | 0 | 0 | 0 | | FastRand32 | 0.20 | | | | | 11 | 106 | *too many* | | FastRand63 | 0.21 | | | | | 0 | 5 | 7 | | LFib78 | 0.35 | | | | | 0 | 0 | 0 | diff --git a/testCPUPerfs.py b/testCPUPerfs.py index b3ce2f6..b098d15 100644 --- a/testCPUPerfs.py +++ b/testCPUPerfs.py @@ -51,22 +51,23 @@ def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: in test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) test_perf("Cwg128_64" , 0x3ca5_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("MRGRand287" , 0x3ca5_8796 , 100_000, N) - test_perf("MRGRand1457" , 0x3ca5_8796 , 100_000, N) - test_perf("MRGRand49507", 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("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("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("MRGRand287" , 0x3ca5_8796 , 100_000, N) + #test_perf("MRGRand1457" , 0x3ca5_8796 , 100_000, N) + #test_perf("MRGRand49507", 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("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) #===== end of module testCPUPerfs.py =================================== diff --git a/testED.py b/testED.py index 6955a3f..0e193cc 100644 --- a/testED.py +++ b/testED.py @@ -111,6 +111,7 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): if __name__ == "__main__": test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number test_algo(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) From 4f021dddf9f319869a50b935a4432d484236f40b Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 28 Feb 2025 15:11:27 +0100 Subject: [PATCH 09/12] #79-add CWG short documentation in README.md Done. --- README.md | 83 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0594944..605786b 100644 --- a/README.md +++ b/README.md @@ -177,19 +177,22 @@ The call operator (i.e., '()') gets a new signature which is still backward com Version 2.0 of **PyRandLib** implements some new other "recent" PRNGs - see them listed below. It also provides two test scripts, enhanced documentation and some other internal development features: 1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6], 2006) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (up to 1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. -In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b. +In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b which all generate output values coded on 32-bits. -1. The PCG (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and clearly explained in technical report [7]. It offers jumping, hard to discover internal state and multi-streams featured. It passes all crush and big crush tests of TestU01. -**PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). +1. The PCG algorithm (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and clearly explained in technical report [7]. It offers jumping, hard to discover internal state and multi-streams featured. It passes all crush and big crush tests of TestU01. +**PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities: Pcg64_32, Pcg128-64 and Pcg1024_32 classes which generate output values coded on resp. 32-, 64- and 32- bits. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). -1. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. +1. The CWG algorithm (Collatz-Weyl Generator, see [8], 2024) is now implemented in **PyRandLib**. This algorithm is fast, uses four integers as its internal state and generates chaos via multiplication and xored-shifted instructions. Periods are medium to large and the generated rendomness is of up quality. It does not offer jump ahead but multi-streams feature is available via the simple modification of well specified one of the four integers. +2. In **PyRandLib**, the CWG algorithm is provided in next forms: Cwg64, Cwg64-128 and Cwg128 which generate output values coded on resp. 64-, 64- and 128- bits . + +3. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. It is now **highly recommended** to not use previous releases (aka. 1.x) of **PyRandLib**. 1. Another short script `testCPUPerfs.py` is now avaliable for testing CPU performance of the different implemented algorithms. It has been used to enhance this documentation by providing a new *times evaluation* table. -1. Documentation has been enhanced, with typos and erroneous docstrings fixed also. +2. Documentation has been enhanced, with typos and erroneous docstrings fixed also. -1. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. +3. All developments are now done under a newly created branch named `dev`. This development branch may be derived into sub-branches for the development of new features. Merges from `dev` to branch `main` only happen when creating new releases. So, if you want to see what is currently going on for next release, just check-out branch `dev`. 1. A Github project dedicated to **PyRandLib** has been created: the [pyrandlib](https://github.com/users/schmouk/projects/14) project. @@ -223,13 +226,60 @@ Notice: Since PyRandLib 2.0, class `BaseRandom` implements the new method `next( Since version 2.0 of PyRandLib also, the newly implemented method `getrandbits()` overrides the same method of Python built-in base class `random.Random`. + +### Cwg64 - minimum 2^70 period + +**Cwg64** implements the full 64 bits version of the Collatz-Weyl Generator algorithm: computations are done on 64-bits, the output generated value is coded on 64-bits also. It provides a medium period which is at minimum 2^70 (i.e. about 1.18e+21), short computation time and a four 64-bits integers internal state (x, a, weyl, s). The internal state is defined by 64-bits control values x, a, weyl and s. + +This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as the combination of the next instructions applied to *state(i-1)*: + + a(i) = a(i-1) + x(i-1) + weyl(i) = weyl(i-1) + s // s is constant over time and must be odd, this is the value to modify to get multi-streams + x(i) = ((x(i-1) >> 1) * ((a(i)) | 1)) ^ (weyl(i))) + output(i) = a(i) >> 48 ^ x(i) + +See Cwg128_64 for a (minimum) 2^71 period (i.e. about 2.36e+21) and one 128-bits plus three 64-bits integers internal state. +See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits integers internal state. + + +### Cwg128_64 - minimum 2^71 period + +**Cwg128_64** implements the mixed 128/64 bits version of the Collatz-Weyl Generator algorithm: computations are done on 128- and 64-bits, the output generated value is coded on 64-bits also. It provides a medium period which is at minimum 2^71 (i.e. about 2.36e+21), short computation time and a three 64-bits (a, weyl, s) plus one 128-bits integers internal state (x). The internal state is defined by 64-bits control values x, a, weyl and s. + +This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as the combination of the next instructions applied to *state(i-1)*: + + a(i) = a(i-1) + x(i-1) + weyl(i) = weyl(i+1) + s // s is constant over time and must be odd, this is the value to modify to get multi-streams + x(i) = ((x(i-1) | 1) * (a(i) >> 1)) ^ (weyl(i)) + output(i) = a(i) >> 48 ^ x(i) + +See Cwg64 for a (minimum) 2^70 period (i.e. about 1.18e+21) and four 64-bits integers internal state. +See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits integers internal state. + + + +### Cwg128 - minimum 2^135 period + +**Cwg128** implements the full 128 bits version of the Collatz-Weyl Generator algorithm: computations are done on 128-bits, the output generated value is coded on 128-bits also. It provides a medium period which is at minimum 2^135 (i.e. about 4.36e+40), short computation time and a four 128-bits integers internal state (x, a, weyl, s). The internal state is defined by 64-bits control values x, a, weyl and s. + +This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as the combination of the next instructions applied to *state(i-1)*: + + a(i) = a(i-1) + x(i-1) + weyl(i) = weyl(i-1) + s // s is constant over time and must be odd, this is the value to modify to get multi-streams + x(i) = ((x(i-1) >> 1) * ((a(i)) | 1)) ^ (weyl(i))) + output(i) = a(i) >> 96 ^ x(i) + +See Cwg64 for a (minimum) 2^70 period (i.e. about 1.18e+21) and four 64-bits integers internal state. +See Cwg128_64 for a (minimum) 2^71 period (i.e. about 2.36e+21) and one 128-bits plus three 64-bits integers internal state. + + + ### FastRand32 - 2^32 periodicity **FastRand32** implements a Linear Congruential Generator dedicated to 32-bits calculations with very short period (about 4.3e+09) but very short time computation. -LCG models evaluate pseudo-random numbers suites *x(i)* as a simple -mathematical function of *x(i-1)*: +LCG models evaluate pseudo-random numbers suites *x(i)* as a simple mathematical function of *x(i-1)*: x(i) = ( a * x(i-1) + c ) mod m @@ -237,14 +287,13 @@ The implementation of **FastRand32** is based on (*a*=69069, *c*=1) since thes Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use such pseudo-random numbers generators for serious simulation applications. -See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with low computation time and *better* randomness characteristics. +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. +**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 model evaluate pseudo-random numbers suites *x(i)* as a simple mathematical function of *x(i-1)*: @@ -254,15 +303,13 @@ The implementation of this LCG 63-bits model is based on (*a*=921974142649997144 Results are nevertheless considered to be poor as stated in the evaluation done by Pierre L'Ecuyer and Richard Simard. Therefore, it is not recommended to use this pseudo-random numbers generatorsfor serious simulation applications, even if FastRandom63 fails on very far less tests than does FastRandom32. -See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% -lower computation time. +See FastRand32 for a 2^32 period (i.e. about 4.3e+09) LC-Generator with 25% lower computation time. ### 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 +**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 @@ -278,11 +325,9 @@ The implementation of **LFibRand78** is based on a Lagged Fibonacci generator ( 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 32-bits coded). +It offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due to the use of a 2^64 modulo (less than twice the computation time of LCGs) and low memory consumption (17 integers 32-bits coded). -Please notice that the TestUO1 article states that the operator should be '*' while George Marsaglia in its original article [4] used the operator -'+'. We've implemented in **PyRandLib** the original operator '+'. +Please notice that the 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 '+'. From b726f3041a9654581c4a8285afaa374b5a70230c Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 28 Feb 2025 15:16:36 +0100 Subject: [PATCH 10/12] #79-add CWG short documentation in README.md fixed a few typos. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 605786b..b58aad8 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Up to now, it has only been run with a Python 3.9.13 (64-bits) virtual environme | PyRabndLib class | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | SmallCrush fails | Crush fails | BigCrush fails | | ---------------- | ---------- | ----------- | ----------- | ----------- | ----------- | ---------------- | ----------- | -------------- | | Cwg64 | 0.60 | | | | | 0 | 0 | 0 | - | Cwg128_64_ | 0.60 | | | | | 0 | 0 | 0 | + | Cwg128_64 | 0.60 | | | | | 0 | 0 | 0 | | Cwg128 | 0.63 | | | | | 0 | 0 | 0 | | FastRand32 | 0.20 | | | | | 11 | 106 | *too many* | | FastRand63 | 0.21 | | | | | 0 | 5 | 7 | @@ -182,7 +182,7 @@ In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well10 1. The PCG algorithm (Permuted Congruential Generator, see [7], 2014) is now implemented in **PyRandLib**. This algorithm is a very fast and enhanced on randomness quality version of Linear Congruential Generators. It is based on solid Mathematics foundation and clearly explained in technical report [7]. It offers jumping, hard to discover internal state and multi-streams featured. It passes all crush and big crush tests of TestU01. **PyRandLib** implements its 3 major versions with resp. 2^32, 2^64 and 2^128 periodicities: Pcg64_32, Pcg128-64 and Pcg1024_32 classes which generate output values coded on resp. 32-, 64- and 32- bits. The original library (C and C++) can be downloaded here: [https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip](https://www.pcg-random.org/downloads/pcg-cpp-0.98.zip) as well as can code be cloned from here: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp). -1. The CWG algorithm (Collatz-Weyl Generator, see [8], 2024) is now implemented in **PyRandLib**. This algorithm is fast, uses four integers as its internal state and generates chaos via multiplication and xored-shifted instructions. Periods are medium to large and the generated rendomness is of up quality. It does not offer jump ahead but multi-streams feature is available via the simple modification of well specified one of the four integers. +1. The CWG algorithm (Collatz-Weyl Generator, see [8], 2024) is now implemented in **PyRandLib**. This algorithm is fast, uses four integers as its internal state and generates chaos via multiplication and xored-shifted instructions. Periods are medium to large and the generated randomness is of up quality. It does not offer jump ahead but multi-streams feature is available via the simple modification of well specified one of the four integers. 2. In **PyRandLib**, the CWG algorithm is provided in next forms: Cwg64, Cwg64-128 and Cwg128 which generate output values coded on resp. 64-, 64- and 128- bits . 3. A short script `testED.py` is now avalibale at root directory. It checks the equi-distribution of every PRNG implemented in **PyRandLib** in a simple way and is used to test for their maybe bad implementation within the library. Since release 2.0 this test is run on all PRNGs. @@ -229,14 +229,14 @@ Since version 2.0 of PyRandLib also, the newly implemented method `getrandbits() ### Cwg64 - minimum 2^70 period -**Cwg64** implements the full 64 bits version of the Collatz-Weyl Generator algorithm: computations are done on 64-bits, the output generated value is coded on 64-bits also. It provides a medium period which is at minimum 2^70 (i.e. about 1.18e+21), short computation time and a four 64-bits integers internal state (x, a, weyl, s). The internal state is defined by 64-bits control values x, a, weyl and s. +**Cwg64** implements the full 64 bits version of the Collatz-Weyl Generator algorithm: computations are done on 64-bits, the output generated value is coded on 64-bits also. It provides a medium period which is at minimum 2^70 (i.e. about 1.18e+21), short computation time and a four 64-bits integers internal state (x, a, weyl, s). This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as the combination of the next instructions applied to *state(i-1)*: a(i) = a(i-1) + x(i-1) weyl(i) = weyl(i-1) + s // s is constant over time and must be odd, this is the value to modify to get multi-streams x(i) = ((x(i-1) >> 1) * ((a(i)) | 1)) ^ (weyl(i))) - output(i) = a(i) >> 48 ^ x(i) + output(i) = (a(i) >> 48) ^ x(i) See Cwg128_64 for a (minimum) 2^71 period (i.e. about 2.36e+21) and one 128-bits plus three 64-bits integers internal state. See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits integers internal state. @@ -244,14 +244,14 @@ See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits inte ### Cwg128_64 - minimum 2^71 period -**Cwg128_64** implements the mixed 128/64 bits version of the Collatz-Weyl Generator algorithm: computations are done on 128- and 64-bits, the output generated value is coded on 64-bits also. It provides a medium period which is at minimum 2^71 (i.e. about 2.36e+21), short computation time and a three 64-bits (a, weyl, s) plus one 128-bits integers internal state (x). The internal state is defined by 64-bits control values x, a, weyl and s. +**Cwg128_64** implements the mixed 128/64 bits version of the Collatz-Weyl Generator algorithm: computations are done on 128- and 64-bits, the output generated value is coded on 64-bits also. It provides a medium period which is at minimum 2^71 (i.e. about 2.36e+21), short computation time and a three 64-bits (a, weyl, s) plus one 128-bits integer internal state (x). This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as the combination of the next instructions applied to *state(i-1)*: a(i) = a(i-1) + x(i-1) weyl(i) = weyl(i+1) + s // s is constant over time and must be odd, this is the value to modify to get multi-streams x(i) = ((x(i-1) | 1) * (a(i) >> 1)) ^ (weyl(i)) - output(i) = a(i) >> 48 ^ x(i) + output(i) = (a(i) >> 48) ^ x(i) See Cwg64 for a (minimum) 2^70 period (i.e. about 1.18e+21) and four 64-bits integers internal state. See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits integers internal state. @@ -260,14 +260,14 @@ See Cwg128 for a (minimum) 2^135 (i.e. about 4.36e+40) and a four 128-bits inte ### Cwg128 - minimum 2^135 period -**Cwg128** implements the full 128 bits version of the Collatz-Weyl Generator algorithm: computations are done on 128-bits, the output generated value is coded on 128-bits also. It provides a medium period which is at minimum 2^135 (i.e. about 4.36e+40), short computation time and a four 128-bits integers internal state (x, a, weyl, s). The internal state is defined by 64-bits control values x, a, weyl and s. +**Cwg128** implements the full 128 bits version of the Collatz-Weyl Generator algorithm: computations are done on 128-bits, the output generated value is coded on 128-bits also. It provides a medium period which is at minimum 2^135 (i.e. about 4.36e+40), short computation time and a four 128-bits integers internal state (x, a, weyl, s). This version of the CGW algorithm evaluates pseudo-random suites *output(i)* as the combination of the next instructions applied to *state(i-1)*: a(i) = a(i-1) + x(i-1) weyl(i) = weyl(i-1) + s // s is constant over time and must be odd, this is the value to modify to get multi-streams x(i) = ((x(i-1) >> 1) * ((a(i)) | 1)) ^ (weyl(i))) - output(i) = a(i) >> 96 ^ x(i) + output(i) = (a(i) >> 96) ^ x(i) See Cwg64 for a (minimum) 2^70 period (i.e. about 1.18e+21) and four 64-bits integers internal state. See Cwg128_64 for a (minimum) 2^71 period (i.e. about 2.36e+21) and one 128-bits plus three 64-bits integers internal state. From fbab72fcaed10fddce04fad412968caa902b8ad3 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 28 Feb 2025 22:57:56 +0100 Subject: [PATCH 11/12] #95-implement base class BaseSquares Completed. --- PyRandLib/basecwg.py | 4 +- PyRandLib/baserandom.py | 2 +- PyRandLib/basesquares.py | 104 +++++++++++++++++++++++++++++++++++++++ README.md | 7 +++ 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 PyRandLib/basesquares.py diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py index 339a78e..560b735 100644 --- a/PyRandLib/basecwg.py +++ b/PyRandLib/basecwg.py @@ -27,7 +27,7 @@ #============================================================================= class BaseCWG( BaseRandom ): - """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. This module is part of library PyRandLib. @@ -64,7 +64,7 @@ class BaseCWG( BaseRandom ): 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 + 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 | diff --git a/PyRandLib/baserandom.py b/PyRandLib/baserandom.py index 30a5cca..879fdd1 100644 --- a/PyRandLib/baserandom.py +++ b/PyRandLib/baserandom.py @@ -330,7 +330,7 @@ def seed(self, _seed: SeedStateType = None) -> None: def __call__(self, _max : Union[Numerical, Tuple[Numerical], List[Numerical]] = 1.0, - times: int = 1 ) -> Numerical: + times: int = 1 ) -> Union[Numerical | List[Numerical]]: """This class's instances are callable. The returned value is uniformly contained within the diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py new file mode 100644 index 0000000..3650f4c --- /dev/null +++ b/PyRandLib/basesquares.py @@ -0,0 +1,104 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF 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, StatesListAndState + + +#============================================================================= +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. + + 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 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. + """ + + #------------------------------------------------------------------------- + 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) -> StatesListAndState: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + +#===== end of module basesquares.py ==================================== diff --git a/README.md b/README.md index b58aad8..0f012a9 100644 --- a/README.md +++ b/README.md @@ -746,3 +746,10 @@ see also [https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf](https://www.pcg-r Published at arXiv, December 2023 (11 pages) Last reference: arXiv:2312.17043v4 [cs.CE], 2 Dec 2024, see [https://arxiv.org/abs/2312.17043](https://arxiv.org/abs/2312.17043). + + +**[9]** Bernard Widynski. March 2022. +*Squares: A Fast Counter-Based RNG*. +Published at arXiv, March 2022 (5 pages) +Last reference: arXiv:2004.06278v7 [cs.DS] 13 Mar 2022 +see [https://arxiv.org/pdf/2004.06278](https://arxiv.org/pdf/2004.06278). From dc516fd984a94f4277ba0b7ae71110e38257bc29 Mon Sep 17 00:00:00 2001 From: Philippe Schmouker Date: Fri, 28 Feb 2025 23:25:25 +0100 Subject: [PATCH 12/12] #93-implement class Squares32 Completed. --- PyRandLib/basesquares.py | 4 +- PyRandLib/squares32.py | 167 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 PyRandLib/squares32.py diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py index 3650f4c..194ff9f 100644 --- a/PyRandLib/basesquares.py +++ b/PyRandLib/basesquares.py @@ -22,7 +22,7 @@ #============================================================================= from .baserandom import BaseRandom -from .annotation_types import SeedStateType, StatesListAndState +from .annotation_types import SeedStateType, StatesList #============================================================================= @@ -87,7 +87,7 @@ def __init__(self, _seedState: SeedStateType = None) -> None: #------------------------------------------------------------------------- - def getstate(self) -> StatesListAndState: + def getstate(self) -> StatesList: """Returns an object capturing the current internal state of the generator. This object can be passed to setstate() to restore the state. diff --git a/PyRandLib/squares32.py b/PyRandLib/squares32.py new file mode 100644 index 0000000..07b0b17 --- /dev/null +++ b/PyRandLib/squares32.py @@ -0,0 +1,167 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF 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 .fastrand32 import FastRand32 +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 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. + """ + + #------------------------------------------------------------------------- + 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. + """ + ''' + inline static uint32_t squares32(uint64_t ctr, uint64_t key) { + uint64_t x, y, z; + y = x = ctr * key; z = y + key; + x = x*x + y; x = (x>>32) | (x<<32); /* round 1 */ + x = x*x + z; x = (x>>32) | (x<<32); /* round 2 */ + x = x*x + y; x = (x>>32) | (x<<32); /* round 3 */ + return (x*x + z) >> 32; /* round 4 */ + ''' + self._counter += 1 + self._counter &= 0xffff_ffff_ffff_ffff + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """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._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> 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._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( FastRand32(int(_state + 0.5) & 0xffff_ffff_ffff_ffff) ) + else: + self._key = self._initKey( FastRand32(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 + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey( FastRand32() ) + +#===== end of module squares32.py ======================================