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/PyRandLib/__init__.py b/PyRandLib/__init__.py index 650b04e..bd75909 100644 --- a/PyRandLib/__init__.py +++ b/PyRandLib/__init__.py @@ -6,21 +6,39 @@ Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com """ +from .basecwg import BaseCWG from .baselcg import BaseLCG from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG from .basemrg import BaseMRG from .baserandom import BaseRandom +from .basesquares import BaseSquares from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 from .fastrand32 import FastRand32 from .fastrand63 import FastRand63 from .lfib78 import LFib78 from .lfib116 import LFib116 from .lfib668 import LFib668 from .lfib1340 import LFib1340 -from .mrgrand287 import MRGRand287 -from .mrgrand1457 import MRGRand1457 -from .mrgrand49507 import MRGRand49507 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 from .well512a import Well512a from .well1024a import Well1024a from .well19937c import Well19937c from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib/annotation_types.py b/PyRandLib/annotation_types.py index 2d55886..e097004 100644 --- a/PyRandLib/annotation_types.py +++ b/PyRandLib/annotation_types.py @@ -23,9 +23,11 @@ #============================================================================= from typing import List, Tuple, Union -Numerical = Union[ int, float ] -StateType = Union[ Tuple[Numerical], List[Numerical], Tuple[List[Numerical], int], Tuple[Tuple[Numerical], int] ] -SeedStateType = Union[ Numerical, StateType ] +Numerical = Union[ int, float ] +StatesList = Union[ Tuple[int], List[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] #===== end of PyRandLib.annotation_types =============================== diff --git a/PyRandLib/basecwg.py b/PyRandLib/basecwg.py new file mode 100644 index 0000000..40c2aa5 --- /dev/null +++ b/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/PyRandLib/baselcg.py b/PyRandLib/baselcg.py index ec02820..acd6ce2 100644 --- a/PyRandLib/baselcg.py +++ b/PyRandLib/baselcg.py @@ -21,7 +21,8 @@ """ #============================================================================= -from .baserandom import BaseRandom +from .baserandom import BaseRandom +from .annotation_types import Numerical #============================================================================= @@ -37,12 +38,12 @@ class BaseLCG( BaseRandom ): x(i-1): x(i) = (a*x(i-1) + c) mod m - Results are nevertheless considered to be poor as stated in the - evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de - Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number - Generators - ACM Transactions on Mathematical Software, vol.33 n.4, - pp.22-40, August 2007'. It is not recommended to use such pseudo-random - numbers generators for serious simulation applications. + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low computation time but shorter period and worse randomness characteristics @@ -52,73 +53,52 @@ class BaseLCG( BaseRandom ): characteristics than for FastRand32. Furthermore this class is callable: - rand = BaseLCG() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a pseudo-random value within [a , b) + 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. - | 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 | + | 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; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - #------------------------------------------------------------------------= - def __init__(self, _seedState: int = None) -> None: + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: """Constructor. - Should inSeed be None or not an integer then the local - time is used (with its shuffled value) as a seed. + 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 call creates attribute self._value and sets it + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes - #------------------------------------------------------------------------= - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - Inheriting classes HAVE TO IMPLEMENT this method - see FastRand32 - for an example. It should use and initialize attribute self._value. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------= + #------------------------------------------------------------------------- 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._value - - - #------------------------------------------------------------------------= - def setstate(self, _state: int) -> None: - """Restores the internal state of the generator. - - _state should have been obtained from a previous call to getstate(), - and setstate() restores the internal state of the generator to what - it was at the time setstate() was called. - Inheriting classes HAVE TO IMPLEMENT this method - see FastRand32 - for an example. It should initialize attribute self._value. - """ - raise NotImplementedError() - + return self._state #===== end of module baselcg.py ======================================== diff --git a/PyRandLib/baselfib64.py b/PyRandLib/baselfib64.py index 14c4817..96da67d 100644 --- a/PyRandLib/baselfib64.py +++ b/PyRandLib/baselfib64.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- """ Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com @@ -24,16 +22,17 @@ #============================================================================= from .baserandom import BaseRandom -from .fastrand32 import FastRand32 -from .annotation_types import SeedStateType, StateType +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 #============================================================================= class BaseLFib64( BaseRandom ): - """The base class for all LFib PRG based on 64-bits numbers. + """The base class for all LFib PRNG based on 64-bits numbers. Definition of the base class for all LFib pseudo-random generators based on 64-bits generated numbers. + This module is part of library PyRandLib. Copyright (c) 2016-2025 Philippe Schmouker @@ -65,10 +64,10 @@ class BaseLFib64( BaseRandom ): Please notice that this class and all its inheriting sub-classes are callable. Example: - rand = BaseLFib() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a pseudo-random value within [a , b) + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an example. @@ -78,22 +77,38 @@ class BaseLFib64( BaseRandom ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - - #------------------------------------------------------------------------= + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- def __init__(self, _seedState: SeedStateType = None) -> None: """Constructor. @@ -108,46 +123,35 @@ def __init__(self, _seedState: SeedStateType = None) -> None: initial seed. """ super().__init__( _seedState ) - # this call creates the two attributes + # this call creates the two attributes # self._state and self._index, and sets them # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------= - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - Inheriting classes HAVE TO IMPLEMENT this method - see LFib78 - for an example. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------= + + + #------------------------------------------------------------------------- def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. - This object can be passed to setstate() to restore the state. It is a + This object can be passed to setstate() to restore the state. It is a tuple containing a list of self._STATE_SIZE integers and an index in this list (index value being then in range(0,self._STATE_SIZE). """ return (self._state[:], self._index) - - - #------------------------------------------------------------------------= + + + #------------------------------------------------------------------------- def setstate(self, _seedState: StateType) -> None: """Restores the internal state of the generator. _seedState should have been obtained from a previous call to getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else (e.g. None) then the shuffling of the local current time value is used as such an initial seed. """ @@ -155,34 +159,37 @@ def setstate(self, _seedState: StateType) -> None: count = len( _seedState ) if count == 0: - self._initIndex( 0 ) - self._initState() + self._index = 0 + self._initstate() elif count == 1: - self._initIndex( 0 ) - self._initState( _seedState[0] ) + self._index = 0 + self._initstate( _seedState[0] ) else: - self._initIndex( _seedState[1] ) - self._state = _seedState[0][:] + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) except: - self._initIndex( 0 ) - self._initState( _seedState ) - + self._index = 0 + self._initstate( _seedState ) + - #------------------------------------------------------------------------= - def _initIndex(self, _index: int) -> None: + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: """Inits the internal index pointing to the internal list. """ try: self._index = int( _index ) % self._STATE_SIZE except: self._index = 0 - - - #------------------------------------------------------------------------= - def _initState(self, _initialSeed: StateType = None) -> None: + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: """Inits the internal list of values. Inits the internal list of values according to some initial @@ -190,8 +197,9 @@ def _initState(self, _initialSeed: StateType = None) -> None: [0.0, 1.0). Should it be None or anything else then the current local time value is used as initial seed value. """ - myRand = FastRand32( _initialSeed ) - self._state = [ (int(myRand(0x1_0000_0000)) << 32) + int(myRand(0x1_0000_0000)) for _ in range(self._STATE_SIZE) ] + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + #===== end of module baselfib64.py ===================================== diff --git a/PyRandLib/basemelg.py b/PyRandLib/basemelg.py new file mode 100644 index 0000000..e69665b --- /dev/null +++ b/PyRandLib/basemelg.py @@ -0,0 +1,199 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/PyRandLib/basemrg.py b/PyRandLib/basemrg.py index 99c493f..8059c8e 100644 --- a/PyRandLib/basemrg.py +++ b/PyRandLib/basemrg.py @@ -22,8 +22,8 @@ #============================================================================= from .baserandom import BaseRandom -from .fastrand32 import FastRand32 -from .annotation_types import SeedStateType, StateType +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 #============================================================================= @@ -48,58 +48,57 @@ class BaseMRG( BaseRandom ): vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random numbers generators rather than LCG ones for serious simulation applications. - See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low computation time but 256 integers memory consumption. - See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and - longer computation time (2^31-1 modulus calculations) but less memory space - consumption (47 integers). - See MRGRand49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower computation time too (32-bits modulus) but use of more memory space (1597 integers). Please notice that this class and all its inheriting sub-classes are callable. Example: - rand = BaseMRG() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a pseudo-random value within [a , b) + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. - See MRGRand287 for an example. + See Mrg287 for an example. Reminder: We give you here below a copy of the table of tests for the MRGs that have been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - #------------------------------------------------------------------------= + #------------------------------------------------------------------------- def __init__(self, _seedState: SeedStateType = None) -> None: """Constructor. - _seedState is either a valid state, an integer, a float or None. + _seedState is either a valid state, an integer, a float or None. About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value + self._STATE_SIZE integers and an index in this list (index value being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. """ super().__init__( _seedState ) # this call creates the two attributes @@ -107,41 +106,30 @@ def __init__(self, _seedState: SeedStateType = None) -> None: # since it internally calls self.setstate(). - #------------------------------------------------------------------------= - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - Inheriting classes HAVE TO IMPLEMENT this method - see MRGRand287 - for an example. - """ - raise NotImplementedError() - - - #------------------------------------------------------------------------= + #------------------------------------------------------------------------- def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an - index in this list (index value being then in range(0,self._STATE_SIZE). + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). """ return (self._state[:], self._index) - #------------------------------------------------------------------------= + #------------------------------------------------------------------------- def setstate(self, _seedState: StateType) -> None: """Restores the internal state of the generator. _seedState should have been obtained from a previous call to getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (31-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else (e.g. None) then the shuffling of the local current time value is used as such an initial seed. """ @@ -149,24 +137,27 @@ def setstate(self, _seedState: StateType) -> None: count = len( _seedState ) if count == 0: - self._initIndex( 0 ) - self._initState() + self._index = 0 + self._initstate() elif count == 1: - self._initIndex( 0 ) - self._initState( _seedState[0] ) + self._index = 0 + self._initstate( _seedState[0] ) else: - self._initIndex( _seedState[1] ) - self._state = _seedState[0][:] + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) except: - self._initIndex( 0 ) - self._initState( _seedState ) + self._index = 0 + self._initstate( _seedState ) - #------------------------------------------------------------------------= - def _initIndex(self, _index: int) -> None: + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: """Inits the internal index pointing to the internal list. """ try: @@ -175,8 +166,8 @@ def _initIndex(self, _index: int) -> None: self._index = 0 - #------------------------------------------------------------------------= - def _initState(self, _initialSeed: StateType = None) -> None: + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: """Inits the internal list of values. Inits the internal list of values according to some initial @@ -185,8 +176,8 @@ def _initState(self, _initialSeed: StateType = None) -> None: current local time value is used as initial seed value. """ # feeds the list according to an initial seed and the value+1 of the modulo. - myRand = FastRand32( _initialSeed ) - self._state = [ int(myRand(self._MODULO+1)) for _ in range(self._STATE_SIZE) ] + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] #===== end of module basemrg.py ======================================== diff --git a/PyRandLib/basepcg.py b/PyRandLib/basepcg.py new file mode 100644 index 0000000..8c9a423 --- /dev/null +++ b/PyRandLib/basepcg.py @@ -0,0 +1,117 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module basepcg.py ======================================== diff --git a/PyRandLib/baserandom.py b/PyRandLib/baserandom.py index a27574a..12b8598 100644 --- a/PyRandLib/baserandom.py +++ b/PyRandLib/baserandom.py @@ -22,7 +22,7 @@ #============================================================================= from random import Random -from typing import Any, List, Tuple, Union +from typing import List, Tuple, Union from .annotation_types import Numerical, SeedStateType, StateType @@ -35,34 +35,51 @@ class BaseRandom( Random ): Copyright (c) 2016-2025 Philippe Schmouker - See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator and FastRand63 for a - 2^63 (i.e. about 9.2e+18) period LC-Generator with low computation time. - - See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low - computation time but 256 integers memory consumption. - See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and - longer computation time (2^31-1 modulus calculations) but less memory space - consumption (47 integers). - See MRGRand49507 for a far longer period (2^49507, i.e. 1.2e+14903) with low - computation time too (31-bits modulus) but use of more memory space (1597 - integers). - - See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for long period LFib + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and - 1279 integers). + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. Python built-in class random.Random is subclassed here to use a different basic generator of our own devising: in that case, overriden methods are: random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). Furthermore this class and all its inheriting sub-classes are callable. Example: - rand = BaseRandom() - 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) + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Please notice that for simulating the roll of a dice you should program: diceRoll = UFastRandom() @@ -70,7 +87,7 @@ class BaseRandom( Random ): Such a programming is a simplified while still robust emulation of inherited methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). - Inheriting random.Random, next methods are available: + Inheriting from random.Random, next methods are also available: | | betavariate(self, alpha, beta) | Beta distribution. @@ -107,6 +124,11 @@ class BaseRandom( Random ): | | Not thread-safe without a lock around calls. | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | | | getstate(self) | Return internal state; can be passed to setstate() later. @@ -130,6 +152,13 @@ class BaseRandom( Random ): | Pareto distribution. alpha is the shape parameter. | | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | | randint(self, a, b) | Return random integer in range [a, b], including both end points. | @@ -211,26 +240,104 @@ class BaseRandom( Random ): | | alpha is the scale parameter and beta is the shape parameter. """ - - #------------------------------------------------------------------------= + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- def __init__(self, _seed: SeedStateType = None) -> None: """Constructor. - Should _seed be None or not 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 ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. """ - super().__init__( _seed ) ## this call creates attribute self._value and sets it + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" - #------------------------------------------------------------------------= - @property - def value(self) -> Any: - """Read-only wrapper to built-in attribute '._value' + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) """ - return self._value + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType) -> None: + """Restores the internal state of the generator. - #------------------------------------------------------------------------= + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- def seed(self, _seed: SeedStateType = None) -> None: """Initiates the internal state of this pseudo-random generator. """ @@ -239,12 +346,12 @@ def seed(self, _seed: SeedStateType = None) -> None: except: super().seed( _seed ) - - #------------------------------------------------------------------------= + + #------------------------------------------------------------------------- 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 @@ -276,4 +383,19 @@ def __call__(self, _max : Union[Numerical, return ret[0] if len(ret) == 1 else ret + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + loMask = (1 << (_bitsCount - _rotCount)) - 1 + hiMask = ((1 << _bitsCount) - 1) ^ loMask + hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) + return ((_value & loMask) << _rotCount) | hiBits + + #===== end of module baserandom.py ===================================== diff --git a/PyRandLib/basesquares.py b/PyRandLib/basesquares.py new file mode 100644 index 0000000..2968237 --- /dev/null +++ b/PyRandLib/basesquares.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/PyRandLib/basewell.py b/PyRandLib/basewell.py index aedcde2..b7dc2ac 100644 --- a/PyRandLib/basewell.py +++ b/PyRandLib/basewell.py @@ -22,8 +22,8 @@ #============================================================================= from .baserandom import BaseRandom -from .fastrand32 import FastRand32 from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 #============================================================================= @@ -34,9 +34,9 @@ class BaseWELL( BaseRandom ): Copyright (c) 2025 Philippe Schmouker - Well-Equilibrated Long-period Linear Generators (WELL) use linear recurrence based - on primitive characteristic polynomials associated with left- and right- shifts - and xor operations to fastly evaluate pseudo-random numbers suites. + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. WELLs offer large to very large periods with best known results in the evaluation of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and @@ -47,11 +47,12 @@ class BaseWELL( BaseRandom ): Furthermore, WELLs have proven their great ability to very fastly escape from zeroland. - Notice: the algorithm in its 4 different versions has been coded here as a direct - implementation of their descriptions in the initial paper "Improved Long-Period - Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre - L’ECUYER (Université de Montréal) and MAKOTO MATSUMOTO (Hiroshima University), in - ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1–16. + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). So, only minimalist optimization has been coded, with the aim at easing the verification of its proper implementation. @@ -68,10 +69,10 @@ class BaseWELL( BaseRandom ): Please notice that this class and all its inheriting sub-classes are callable. Example: - rand = BaseWell() - print( rand() ) # prints a pseudo-random value within [0.0, 1.0) - print( rand(a) ) # prints a pseudo-random value within [0.0, a) - print( rand(a,b) ) # prints a pseudo-random value within [a , b) + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for an example. @@ -81,21 +82,21 @@ class BaseWELL( BaseRandom ): have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when available. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | (1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated tempering algorithm. * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ @@ -103,40 +104,29 @@ class BaseWELL( BaseRandom ): def __init__(self, _seedState: SeedStateType = None) -> None: """Constructor. - _seedState is either a valid state, an integer, a float or None. + _seedState is either a valid state, an integer, a float or None. About valid state: this is a tuple containing a list of - self._STATE_SIZE integers and an index in this list (index value + self._STATE_SIZE integers and an index in this list (index value being then in range(0,self._STATE_SIZE)). Should _seedState be - a sole integer or float then it is used as initial seed for - the random filling of the internal list of self._STATE_SIZE - integers. Should _seedState be anything else (e.g. None) then - the shuffling of the local current time value is used as such an - initial seed. + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ super().__init__( _seedState ) # this call creates the two attributes # self._state and self._index, and sets them # since it internally calls self.setstate(). - - - #------------------------------------------------------------------------- - def random(self) -> float: - """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). - Inheriting classes HAVE TO IMPLEMENT this method - see Well1024a - for an example. - """ - raise NotImplementedError() - + #------------------------------------------------------------------------- def getstate(self) -> StateType: """Returns an object capturing the current internal state of the generator. - This object can be passed to setstate() to restore the state. It is a - tuple containing a list of self._STATE_SIZE integers and an index in this - list (index value being then in range(0,self._STATE_SIZE). + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). """ return (self._state[:], self._index) @@ -147,13 +137,13 @@ def setstate(self, _seedState: StateType) -> None: _seedState should have been obtained from a previous call to getstate(), and setstate() restores the internal state of the - generator to what it was at the time setstate() was called. - About valid state: this is a tuple containing a list of - self._STATE_SIZE integers (32-bits) and an index in this list - (index value being then in range(0,self._STATE_SIZE)). Should + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should _seedState be a sole integer or float then it is used as - initial seed for the random filling of the internal list of - self._STATE_SIZE integers. Should _seedState be anything else + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else (e.g. None) then the shuffling of the local current time value is used as such an initial seed. """ @@ -161,24 +151,27 @@ def setstate(self, _seedState: StateType) -> None: count = len( _seedState ) if count == 0: - self._initIndex( 0 ) - self._initState() + self._index = 0 + self._initstate() elif count == 1: - self._initIndex( 0 ) - self._initState( _seedState[0] ) + self._index = 0 + self._initstate( _seedState[0] ) else: - self._initIndex( _seedState[1] ) - self._state = _seedState[0][:] + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) except: - self._initIndex( 0 ) - self._initState( _seedState ) + self._index = 0 + self._initstate( _seedState ) #------------------------------------------------------------------------- - def _initIndex(self, _index: int) -> None: + def _initindex(self, _index: int) -> None: """Inits the internal index pointing to the internal list. """ try: @@ -188,7 +181,7 @@ def _initIndex(self, _index: int) -> None: #------------------------------------------------------------------------- - def _initState(self, _initialSeed: StateType = None) -> None: + def _initstate(self, _initialSeed: StateType = None) -> None: """Inits the internal list of values. Inits the internal list of values according to some initial @@ -197,8 +190,8 @@ def _initState(self, _initialSeed: StateType = None) -> None: current local time value is used as initial seed value. """ # feeds the list according to an initial seed and the value+1 of the modulo. - myRand = FastRand32( _initialSeed ) - self._state = [ int(myRand(0x1_0000_0000)) for _ in range(self._STATE_SIZE) ] + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] #------------------------------------------------------------------------- @@ -239,7 +232,7 @@ def _M3_neg(cls, x: int, t: int) -> int: @classmethod def _M4(cls, x: int, a: int) -> int: #assert 0 <= a <= 0xffff_ffff - return x >> 1 ^ a if x & 0x8000_0000 else x >> 1 + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 #------------------------------------------------------------------------- @classmethod @@ -277,12 +270,10 @@ def _tempering(cls, x: int, b: int, c: int) -> int: #assert 0 <= b <= 0xffff_ffff #assert 0 <= c <= 0xffff_ffff #assert 0 <= w <= 32 - # z = ((z << (32 - w)) & 0xffff_ffff) >> (32 - w) - # notice: the generic algorithm truncs x on w-bits. All of the implemented - # ones in PyRandLib are set on 32-bits. So, no truncation takes place here - z = x - z = z ^ (((z << 7) & 0xffff_ffff) & b) - return z ^ (((z << 15) & 0xffff_ffff) & c) + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) #------------------------------------------------------------------------- @property @@ -311,6 +302,6 @@ def _a6(self): @property def _a7(self): - return 0xb729_fcec# + return 0xb729_fcec #===== end of module basewell.py ======================================= diff --git a/PyRandLib/basexoroshiro.py b/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..1226391 --- /dev/null +++ b/PyRandLib/basexoroshiro.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: int = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> List[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/PyRandLib/cwg128.py b/PyRandLib/cwg128.py new file mode 100644 index 0000000..22fbab2 --- /dev/null +++ b/PyRandLib/cwg128.py @@ -0,0 +1,159 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: int = (1 << 128) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + +#===== end of module cwg128.py ========================================= diff --git a/PyRandLib/cwg128_64.py b/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..5f7a936 --- /dev/null +++ b/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/PyRandLib/cwg64.py b/PyRandLib/cwg64.py new file mode 100644 index 0000000..7b34c77 --- /dev/null +++ b/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/PyRandLib/fastrand32.py b/PyRandLib/fastrand32.py index 5b3a5af..9813c66 100644 --- a/PyRandLib/fastrand32.py +++ b/PyRandLib/fastrand32.py @@ -21,10 +21,9 @@ """ #============================================================================= -import time - from .baselcg import BaseLCG from .annotation_types import Numerical +from .splitmix import SplitMix32 #============================================================================= @@ -33,6 +32,7 @@ class FastRand32( BaseLCG ): Pseudo-random numbers generator - Linear Congruential Generator dedicated to 32-bits calculations with very short period (about 4.3e+09) but very short time computation. + This module is part of library PyRandLib. Copyright (c) 2016-2025 Philippe Schmouker @@ -72,19 +72,19 @@ class FastRand32( BaseLCG ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | 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; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + #------------------------------------------------------------------------- def __init__(self, _seed: Numerical = None) -> None: """Constructor. @@ -92,19 +92,17 @@ def __init__(self, _seed: Numerical = None) -> None: Should _seed be None or not a numerical then the local time is used (with its shuffled value) as a seed. """ - super().__init__( _seed ) # this call creates attribute self._value and sets it - - + super().__init__( _seed ) # this call creates attribute self._state and sets it + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ - self._value = (69069 * self._value + 1) & 0xffff_ffff - return self._value * 2.328_306_436_538_696_289_062_5e-10 # / 4_294_967_296.0 - - + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + #------------------------------------------------------------------------- def setstate(self, _state: Numerical) -> None: """Restores the internal state of the generator. @@ -114,25 +112,11 @@ def setstate(self, _state: Numerical) -> None: 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._value = _state & 0xffff_ffff - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - if _state >= 1.0: - self._value = int( _state + 0.5 ) & 0xffff_ffff - else: - self._value = int( _state * 0x1_0000_0000) & 0xffff_ffff - + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix32( _state ) + self._state = initRand() else: - # uses local time as initial seed - t = int( time.time() * 1000.0 ) - self._value = ( ((t & 0xff00_0000) >> 24) + - ((t & 0x00ff_0000) >> 8) + - ((t & 0x0000_ff00) << 8) + - ((t & 0x0000_00ff) << 24) ) + initRand = SplitMix32() + self._state = initRand() #===== end of module fastrand32.py ===================================== diff --git a/PyRandLib/fastrand63.py b/PyRandLib/fastrand63.py index 340d523..4483c40 100644 --- a/PyRandLib/fastrand63.py +++ b/PyRandLib/fastrand63.py @@ -21,10 +21,9 @@ """ #============================================================================= -import time - from .baselcg import BaseLCG from .annotation_types import Numerical +from .splitmix import SplitMix63 #============================================================================= @@ -33,6 +32,7 @@ class FastRand63( BaseLCG ): Pseudo-random numbers generator - Linear Congruential Generator dedicated to 63-bits calculations with very short period (about 9.2e+18) and short time computation. + This module is part of library PyRandLib. Copyright (c) 2016-2025 Philippe Schmouker @@ -77,29 +77,43 @@ class FastRand63( BaseLCG ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | 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; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + + #------------------------------------------------------------------------- - def random(self) -> float: + _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ - self._value = (9_219_741_426_499_971_445 * self._value + 1) & 0x7fff_ffff_ffff_ffff - return self._value * 1.084_202_172_485_504_434_007_453e-19 # / 9_223_372_036_854_775_808.0 - - + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- def setstate(self, _state: Numerical) -> None: """Restores the internal state of the generator. @@ -109,25 +123,12 @@ def setstate(self, _state: Numerical) -> None: state of the generator to what it was at the time setstate() was called. """ - if isinstance( _state, int ): - self._value = _state & 0x7fff_ffff_ffff_ffff - - elif isinstance( _state, float ): - # transforms passed initial seed from float to integer - if _state < 0.0 : - _state = -_state - - if _state >= 1.0: - self._value = int(_state + 0.5) & 0x7fff_ffff_ffff_ffff - else: - self._value = int(_state * 0x8000_0000_0000_0000) & 0x7fff_ffff_ffff_ffff - + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix63( _state ) + self._state = initRand() else: - t = int(time.time() * 1000.0) - self._value = t & 0xffff_ffff - self._value += (t & 0xff00_0000) << 8 - self._value += (t & 0x00ff_0000) << 24 - self._value += (t & 0x0000_ff00) << 40 - self._value += (t & 0x0000_00fe) << 63 + initRand = SplitMix63() + self._state = initRand() + #===== end of module fastrand63.py ===================================== diff --git a/PyRandLib/lfib116.py b/PyRandLib/lfib116.py index 166977e..8f1f000 100644 --- a/PyRandLib/lfib116.py +++ b/PyRandLib/lfib116.py @@ -29,6 +29,7 @@ class LFib116( BaseLFib64 ): """ Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci Generator with quite short period (8.3e+34). + This module is part of library PyRandLib. Copyright (c) 2016-2025 Philippe Schmouker @@ -91,33 +92,31 @@ class LFib116( BaseLFib64 ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers - - + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ - # evaluates indexes in suite for the i-24 and i-55 -th values + # evaluates indexes in suite for the i-5 and i-17 -th values k24 = self._index-24 if k24 < 0: k24 += LFib116._STATE_SIZE @@ -128,9 +127,7 @@ def random(self) -> float: # next index self._index = (self._index+1) % LFib116._STATE_SIZE - - # then returns float value within [0.0, 1.0) - return myValue * 5.421_010_862_427_522_170_037_3e-20 # / 18_446_744_073_709_551_616.0 + return myValue #===== end of module lfib116.py ======================================== diff --git a/PyRandLib/lfib1340.py b/PyRandLib/lfib1340.py index cce38a6..5007045 100644 --- a/PyRandLib/lfib1340.py +++ b/PyRandLib/lfib1340.py @@ -29,6 +29,7 @@ class LFib1340( BaseLFib64 ): """ Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci Generator with quite short period (2.4e+403). + This module is part of library PyRandLib. Copyright (c) 2017-2025 Philippe Schmouker @@ -92,31 +93,29 @@ class LFib1340( BaseLFib64 ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers - - + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ # evaluates indexes in suite for the i-861 and i-1279 -th values k861 = self._index-861 @@ -130,8 +129,6 @@ def random(self) -> float: # next index self._index = (self._index+1) % LFib1340._STATE_SIZE - # then returns float value within [0.0, 1.0) - return myValue * 5.421_010_862_427_522_170_037_3e-20 # / 18_446_744_073_709_551_616.0 - + return myValue #===== end of module lfib1340.py ====================================== diff --git a/PyRandLib/lfib668.py b/PyRandLib/lfib668.py index 0d0871e..0f5e494 100644 --- a/PyRandLib/lfib668.py +++ b/PyRandLib/lfib668.py @@ -29,6 +29,7 @@ class LFib668( BaseLFib64 ): """ Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci Generator with quite short period (1.2e+201). + This module is part of library PyRandLib. Copyright (c) 2017-2025 Philippe Schmouker @@ -91,31 +92,29 @@ class LFib668( BaseLFib64 ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers - - + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ # evaluates indexes in suite for the i-273 and i-607 -th values k273 = self._index-273 @@ -129,8 +128,6 @@ def random(self) -> float: # next index self._index = (self._index+1) % LFib668._STATE_SIZE - # then returns float value within [0.0, 1.0) - return myValue * 5.421_010_862_427_522_170_037_3e-20 # / 18_446_744_073_709_551_616.0 + return myValue - #===== end of module lfib668.py ======================================= diff --git a/PyRandLib/lfib78.py b/PyRandLib/lfib78.py index bffd143..7f3589c 100644 --- a/PyRandLib/lfib78.py +++ b/PyRandLib/lfib78.py @@ -29,6 +29,7 @@ class LFib78( BaseLFib64 ): """ Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci Generator with quite short period (3.0e+23). + This module is part of library PyRandLib. Copyright (c) 2016-2025 Philippe Schmouker @@ -90,31 +91,29 @@ class LFib78( BaseLFib64 ): been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | 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 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers - - + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ # evaluates indexes in suite for the i-5 and i-17 -th values k5 = self._index-5 @@ -127,9 +126,7 @@ def random(self) -> float: # next index self._index = (self._index+1) % LFib78._STATE_SIZE - - # then returns float value within [0.0, 1.0) - return myValue * 5.421_010_862_427_522_170_037_3e-20 # / 18_446_744_073_709_551_616.0 + return myValue #===== end of module lfib78.py ========================================= diff --git a/PyRandLib/melg19937.py b/PyRandLib/melg19937.py new file mode 100644 index 0000000..5d9b093 --- /dev/null +++ b/PyRandLib/melg19937.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 311 + self._index = i_1 + + s311 = self._state[311] + + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) + self._state[311] = s311 + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg44497.py b/PyRandLib/melg44497.py new file mode 100644 index 0000000..286cdd7 --- /dev/null +++ b/PyRandLib/melg44497.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 695 + self._index = i_1 + + s695 = self._state[695] + + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) + self._state[695] = s695 + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib/melg607.py b/PyRandLib/melg607.py new file mode 100644 index 0000000..6a38a55 --- /dev/null +++ b/PyRandLib/melg607.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 9 + self._index = i_1 + + s9 = self._state[9] + + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) # M=5, s1=13 + self._state[9] = s9 + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) # s2=35 + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) # s3=30, L=3, b = 0x66ed... + + +#===== end of module melg607.py ======================================== diff --git a/PyRandLib/mrgrand1457.py b/PyRandLib/mrg1457.py similarity index 66% rename from PyRandLib/mrgrand1457.py rename to PyRandLib/mrg1457.py index 02549a2..17f8c32 100644 --- a/PyRandLib/mrgrand1457.py +++ b/PyRandLib/mrg1457.py @@ -25,10 +25,11 @@ #============================================================================= -class MRGRand1457( BaseMRG ): +class Mrg1457( BaseMRG ): """ Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive Generator with long period (3.98e+438). + This module is part of library PyRandLib. Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random @@ -53,9 +54,9 @@ class MRGRand1457( BaseMRG ): and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation time. - See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low computation time but 256 integers memory consumption. - See MRGRand49507 for a far longer period (2^49_507, i.e. 1.2e+14_903) with low + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower computation time too (31-bits modulus) but use of more memory space (1597 integers). @@ -69,7 +70,7 @@ class MRGRand1457( BaseMRG ): print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Notice that for simulating the roll of a dice you should program: - diceRoll = MRGRand1457() + diceRoll = Mrg1457() print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} @@ -82,51 +83,66 @@ class MRGRand1457( BaseMRG ): We give you here below a copy of the table of tests for the MRGs that have been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers - _MODULO = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + - #------------------------------------------------------------------------- - def random(self) -> float: + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values k1 = self._index-1 if k1 < 0: - k1 = MRGRand1457._STATE_SIZE - 1 + k1 = Mrg1457._STATE_SIZE - 1 k24 = self._index-24 if k24 < 0: - k24 += MRGRand1457._STATE_SIZE + k24 += Mrg1457._STATE_SIZE # then evaluates current value - myValue = (67633152 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 self._state[self._index] = myValue # next index - self._index = (self._index + 1) % MRGRand1457._STATE_SIZE - - # then returns float value within [0.0, 1.0) - return myValue * 4.656_612_873_077_039_257_8e-10 # / 2_147_483_648.0 + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + # then returns the integer generated value + return myValue #===== end of module mrgrand1457.py ==================================== diff --git a/PyRandLib/mrgrand287.py b/PyRandLib/mrg287.py similarity index 74% rename from PyRandLib/mrgrand287.py rename to PyRandLib/mrg287.py index 746e4e3..119c3f0 100644 --- a/PyRandLib/mrgrand287.py +++ b/PyRandLib/mrg287.py @@ -25,10 +25,11 @@ #============================================================================= -class MRGRand287( BaseMRG ): +class Mrg287( BaseMRG ): """ Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive Generator with a long period (2.49e+86). + This module is part of library PyRandLib. Copyright (c) 2016-2025 Philippe Schmouker @@ -71,21 +72,21 @@ class MRGRand287( BaseMRG ): and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due to the use of a 2^32 modulo. - See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and - longer computation time (2^31-1 modulus calculations) but less memory space - consumption (47 integers). - See MRGRand49507 for a far longer period (2^49_507, i.e. 1.2e+14_903) with low + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower computation time too (31-bits modulus) but use of more memory space (1_597 integers). Furthermore, this class is callable: - rand = MRGRand287() + rand = Mrg287() print( rand() ) # prints a pseudo-random value within [0.0, 1.0) print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Notice that for simulating the roll of a dice you should program: - diceRoll = MRGRand287() + diceRoll = Mrg287() print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} Such a programming is an accelerated while still robust emulation of the inherited @@ -97,31 +98,29 @@ class MRGRand287( BaseMRG ): We give you here below a copy of the table of tests for the MRGs that have been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers - _MODULO = 4_294_967_295 # i.e. 0xffff_ffff, or (1<<32)-1, the modulo for DX-47-3 MRG - - + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ # The Marsa-LIBF4 version uses the recurrence # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 @@ -129,25 +128,24 @@ def random(self) -> float: # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values k55 = self._index-55 if k55 < 0: - k55 += MRGRand287._STATE_SIZE + k55 += Mrg287._STATE_SIZE k119 = self._index-119 if k119 < 0: - k119 += MRGRand287._STATE_SIZE + k119 += Mrg287._STATE_SIZE k179 = self._index-179 if k179 < 0: - k179 += MRGRand287._STATE_SIZE + k179 += Mrg287._STATE_SIZE # then evaluates current value - myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) % 4_294_967_295 + myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff self._state[self._index] = myValue # next index self._index = (self._index+1) % self._STATE_SIZE - # then returns float value within [0.0, 1.0) - return myValue * 2.328_306_436_538_696_289_062_5e-10 # / 4_294_967_296.0 + # then returns the integer generated value + return myValue - #===== end of module mrgrand287.py ================================== diff --git a/PyRandLib/mrgrand49507.py b/PyRandLib/mrg49507.py similarity index 66% rename from PyRandLib/mrgrand49507.py rename to PyRandLib/mrg49507.py index 6a38cb8..2eb2597 100644 --- a/PyRandLib/mrgrand49507.py +++ b/PyRandLib/mrg49507.py @@ -25,10 +25,11 @@ #============================================================================= -class MRGRand49507( BaseMRG ): +class Mrg49507( BaseMRG ): """ Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive Generator with a very long period (1.17e+14_903). + This module is part of library PyRandLib. Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random @@ -53,24 +54,24 @@ class MRGRand49507( BaseMRG ): and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low computation time. - See MRGRand287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low computation time but 256 integers memory consumption. - See MRGRand1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and - longer computation time (2^31-1 modulus calculations) but less memory space - consumption (47 integers). + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). Class random.Random is sub-subclassed here to use a different basic generator of our own devising: in that case, overriden methods are: random(), seed(), getstate(), and setstate(). Furthermore this class is callable: - rand = MRGRand49507() + rand = Mrg49507() print( rand() ) # prints a pseudo-random value within [0.0, 1.0) print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) Notice that for simulating the roll of a dice you should program: - diceRoll = MRGRand49507() + diceRoll = Mrg49507() print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} @@ -83,45 +84,58 @@ class MRGRand49507( BaseMRG ): We give you here below a copy of the table of tests for the MRGs that have been implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | MRGRand287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | - | MRGRand1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | - | MRGRand49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers - _MODULO = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG - - + _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + #------------------------------------------------------------------------- - def random(self) -> float: + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ # evaluates indexes in suite for the i-7, i-1597 -th values k7 = self._index-7 if k7 < 0: - k7 += MRGRand49507._STATE_SIZE + k7 += Mrg49507._STATE_SIZE # then evaluates current value myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 self._state[self._index] = myValue # next index - self._index = (self._index+1) % MRGRand49507._STATE_SIZE + self._index = (self._index+1) % Mrg49507._STATE_SIZE - # then returns float value within [0.0, 1.0) - return myValue * 4.656_612_873_077_039_257_8e-10 # / 2_147_483_648.0 - + # then returns the integer generated value + return myValue + #===== end of module mrgrand49507.py =================================== diff --git a/PyRandLib/pcg1024_32.py b/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..c721499 --- /dev/null +++ b/PyRandLib/pcg1024_32.py @@ -0,0 +1,280 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _EXTENDED_STATE_SIZE: int = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == self._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + result ^= result >> 22 + + self._extendedState[i] = result + + return result == state & 0b11 + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(self._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/PyRandLib/pcg128_64.py b/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..6f9eb84 --- /dev/null +++ b/PyRandLib/pcg128_64.py @@ -0,0 +1,176 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO_128 : int = (1 << 128) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & self._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + current_state ^= current_state >> 64 # fixed shift XOR is then evaluated + value = current_state & 0xffff_ffff_ffff_ffff + rot_mask = (1 << random_rotation) - 1 + return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & self._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & self._MODULO_128 + else: + self._state = int( _state * (self._MODULO_128 + 1)) & self._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + +#===== end of module pcg128_64.py ====================================== diff --git a/PyRandLib/pcg64_32.py b/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..1fa867e --- /dev/null +++ b/PyRandLib/pcg64_32.py @@ -0,0 +1,155 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + current_state ^= current_state >> 22 # fixed shift XOR is then evaluated + return (current_state >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = initRand() + +#===== end of module pcg64_32.py ======================================= diff --git a/PyRandLib/splitmix.py b/PyRandLib/splitmix.py new file mode 100644 index 0000000..2d674e8 --- /dev/null +++ b/PyRandLib/splitmix.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 diff --git a/PyRandLib/squares32.py b/PyRandLib/squares32.py new file mode 100644 index 0000000..f916093 --- /dev/null +++ b/PyRandLib/squares32.py @@ -0,0 +1,110 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + +#===== end of module squares32.py ====================================== diff --git a/PyRandLib/squares64.py b/PyRandLib/squares64.py new file mode 100644 index 0000000..573f2e2 --- /dev/null +++ b/PyRandLib/squares64.py @@ -0,0 +1,132 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + +#===== end of module squares64.py ====================================== diff --git a/PyRandLib/well1024a.py b/PyRandLib/well1024a.py index 5725a88..9f92c62 100644 --- a/PyRandLib/well1024a.py +++ b/PyRandLib/well1024a.py @@ -26,15 +26,16 @@ #============================================================================= class Well1024a( BaseWELL ): """ - Pseudo-random numbers generator - Definition of a fast 32-bits Well-Equilibrated + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + This module is part of library PyRandLib. Copyright (c) 2025 Philippe Schmouker - Well-Equilibrated Long-period Linear Generators (WELL) use linear recurrence based - on primitive characteristic polynomials associated with left- and right- shifts - and xor operations to fastly evaluate pseudo-random numbers suites. + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. WELLs offer large to very large periods with best known results in the evaluation of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and @@ -46,7 +47,7 @@ class Well1024a( BaseWELL ): zeroland. Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its descriptions in the initial paper: "Improved Long-Period + implementation of its description in the initial paper: "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. @@ -73,7 +74,6 @@ class Well1024a( BaseWELL ): diceRoll = Well1024a() print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} - Such a programming is an accelerated while still robust emulation of the inherited methods: - random.Random.randint(self,1,6) and @@ -84,12 +84,12 @@ class Well1024a( BaseWELL ): have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when available. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | (1)The Well19937c generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated tempering algorithm. @@ -97,23 +97,21 @@ class Well1024a( BaseWELL ): its pseudo-randomness quality, as measured by TestU01. * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) - - + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ i = self._index i_1 = (i - 1) & 0x1f @@ -133,7 +131,6 @@ def random(self) -> float: # version, the zero matrix _M0 which we suppress here for calculations optimization purpose self._index = i_1 - return z3 * 2.328_306_436_538_696_289_062_5e-10 # / 4_294_967_296.0 - + return z3 #===== end of module well1024a.py ====================================== diff --git a/PyRandLib/well19937c.py b/PyRandLib/well19937c.py index 3c3585d..7b836e0 100644 --- a/PyRandLib/well19937c.py +++ b/PyRandLib/well19937c.py @@ -26,15 +26,16 @@ #============================================================================= class Well19937c( BaseWELL ): """ - Pseudo-random numbers generator - Definition of a fast 32-bits Well-Equilibrated + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + This module is part of library PyRandLib. Copyright (c) 2025 Philippe Schmouker - Well-Equilibrated Long-period Linear Generators (WELL) use linear recurrence based - on primitive characteristic polynomials associated with left- and right- shifts - and xor operations to fastly evaluate pseudo-random numbers suites. + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. WELLs offer large to very large periods with best known results in the evaluation of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and @@ -46,7 +47,7 @@ class Well19937c( BaseWELL ): zeroland. Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its descriptions in the initial paper: "Improved Long-Period + implementation of its description in the initial paper: "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. @@ -83,12 +84,12 @@ class Well19937c( BaseWELL ): have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when available. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | (1)The Well19937c generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated tempering algorithm. @@ -96,23 +97,21 @@ class Well19937c( BaseWELL ): its pseudo-randomness quality, as measured by TestU01. * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) - - + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ i = self._index if i >= 2: @@ -131,7 +130,6 @@ def random(self) -> float: self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) self._index = i_1 - return self._tempering(z3, 0xe46e1700, 0x9b868000) * 2.328_306_436_538_696_289_062_5e-10 # / 4_294_967_296.0 - + return self._tempering(z3, 0xe46e1700, 0x9b868000) -#===== end of module well512a.py ======================================= +#===== end of module well19937c.py ===================================== diff --git a/PyRandLib/well44497b.py b/PyRandLib/well44497b.py index 6ca2dba..0157139 100644 --- a/PyRandLib/well44497b.py +++ b/PyRandLib/well44497b.py @@ -26,15 +26,16 @@ #============================================================================= class Well44497b( BaseWELL ): """ - Pseudo-random numbers generator - Definition of a fast 32-bits Well-Equilibrated + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + This module is part of library PyRandLib. Copyright (c) 2025 Philippe Schmouker - Well-Equilibrated Long-period Linear Generators (WELL) use linear recurrence based - on primitive characteristic polynomials associated with left- and right- shifts - and xor operations to fastly evaluate pseudo-random numbers suites. + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. WELLs offer large to very large periods with best known results in the evaluation of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and @@ -46,7 +47,7 @@ class Well44497b( BaseWELL ): zeroland. Notice: the algorithm in its Well1024a version has been coded here as a direct - implementation of its descriptions in the initial paper: "Improved Long-Period + implementation of its description in the initial paper: "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. @@ -83,12 +84,12 @@ class Well44497b( BaseWELL ): have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when available. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | (1)The Well19937c generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated tempering algorithm. @@ -96,23 +97,21 @@ class Well44497b( BaseWELL ): its pseudo-randomness quality, as measured by TestU01. * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) - - + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ i = self._index if i >= 2: @@ -131,7 +130,6 @@ def random(self) -> float: self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 self._index = i_1 - return self._tempering(z3, 0x93dd1400, 0xfa118000) * 2.328_306_436_538_696_289_062_5e-10 # / 4_294_967_296.0 - + return self._tempering(z3, 0x93dd1400, 0xfa118000) -#===== end of module well512a.py ======================================= +#===== end of module Well44497b.py ===================================== diff --git a/PyRandLib/well512a.py b/PyRandLib/well512a.py index c7e7a60..42b14a0 100644 --- a/PyRandLib/well512a.py +++ b/PyRandLib/well512a.py @@ -26,15 +26,16 @@ #============================================================================= class Well512a( BaseWELL ): """ - Pseudo-random numbers generator - Definition of a fast 32-bits Well-Equilibrated + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + This module is part of library PyRandLib. Copyright (c) 2025 Philippe Schmouker - Well-Equilibrated Long-period Linear Generators (WELL) use linear recurrence based - on primitive characteristic polynomials associated with left- and right- shifts - and xor operations to fastly evaluate pseudo-random numbers suites. + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. WELLs offer large to very large periods with best known results in the evaluation of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and @@ -46,7 +47,7 @@ class Well512a( BaseWELL ): zeroland. Notice: the algorithm in its Well512a version has been coded here as a direct - implementation of its descriptions in the initial paper: "Improved Long-Period + implementation of its description in the initial paper: "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. @@ -84,12 +85,12 @@ class Well512a( BaseWELL ): have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when available. - | PyRabndLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | - | ---------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | - | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | - | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | - | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | - | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | (1)The Well19937c generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated tempering algorithm. @@ -97,23 +98,21 @@ class Well512a( BaseWELL ): its pseudo-randomness quality, as measured by TestU01. * _small crush_ is a small set of simple tests that quickly tests some of - the expected characteristics for a pretty good PRG; + the expected characteristics for a pretty good PRNG; * _crush_ is 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 + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG should definitively pass. """ - + #------------------------------------------------------------------------- # 'protected' constant - _STATE_SIZE = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) - - + _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + #------------------------------------------------------------------------- - def random(self) -> float: + def next(self) -> int: """This is the core of the pseudo-random generator. - - Returned values are within [0.0, 1.0). """ i = self._index i_1 = (i - 1) & 0xf @@ -131,7 +130,6 @@ def random(self) -> float: self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) self._index = i_1 - return z3 * 2.328_306_436_538_696_289_062_5e-10 # / 4_294_967_296.0 - + return z3 #===== end of module well512a.py ======================================= diff --git a/PyRandLib/xoroshiro1024.py b/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..62d5cb6 --- /dev/null +++ b/PyRandLib/xoroshiro1024.py @@ -0,0 +1,204 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + #------------------------------------------------------------------------- + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + ''' + const int q = p; + const uint64_t s0 = s[p = (p + 1) & 15]; + uint64_t s15 = s[q]; + const uint64_t result_plus = s0 + s15; + const uint64_t result_plusplus = rotl(s0 + s15, R) + s15; + const uint64_t result_star = s0 * S; + const uint64_t result_starstar = rotl(s0 * S, R) * T; + s15 ^= s0; + s[q] = rotl(s0, A) ^ s15 ^ (s15 << B); + s[p] = rotl(s15, C); + ''' + previousIndex = self._index + # advances the internal state of the PRNG + self._index += 1 + self._index &= self._SIZE_MODULO + sLow = self._state[ self._index ] + sHigh = self._state[ previousIndex ] ^ sLow + self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) + self._state[ self._index ] = self._rotleft( sHigh, 36 ) + # returns the output value + return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & self._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/PyRandLib/xoroshiro256.py b/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..0b09b63 --- /dev/null +++ b/PyRandLib/xoroshiro256.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & self._MODULO + self._s3 = self._rotleft( self._s3, 45 ) + # returns the output value + return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/PyRandLib/xoroshiro512.py b/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..73ed1c8 --- /dev/null +++ b/PyRandLib/xoroshiro512.py @@ -0,0 +1,178 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & self._MODULO + self._state[7] = self._rotleft( self._state[7], 21 ) + # returns the output value + return (self._rotleft( currentS1 * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(8) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.10/PyRandLib/__init__.py b/Python3.10/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.10/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/PyRandLib/LICENSE b/Python3.10/PyRandLib/annotation_types.py similarity index 57% rename from PyRandLib/LICENSE rename to Python3.10/PyRandLib/annotation_types.py index ccab7fa..d5680eb 100644 --- a/PyRandLib/LICENSE +++ b/Python3.10/PyRandLib/annotation_types.py @@ -1,10 +1,9 @@ -MIT License - -Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights +in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -16,6 +15,19 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +#============================================================================= +Numerical = int | float +StatesList = tuple[int] | list[int] +StatesListAndExt = tuple[StatesList, int] +StateType = StatesList | StatesListAndExt +SeedStateType = Numerical | StateType + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.10/PyRandLib/basecwg.py b/Python3.10/PyRandLib/basecwg.py new file mode 100644 index 0000000..d8b51bf --- /dev/null +++ b/Python3.10/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.10/PyRandLib/baselcg.py b/Python3.10/PyRandLib/baselcg.py new file mode 100644 index 0000000..455c230 --- /dev/null +++ b/Python3.10/PyRandLib/baselcg.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.10/PyRandLib/baselfib64.py b/Python3.10/PyRandLib/baselfib64.py new file mode 100644 index 0000000..d1ef6c7 --- /dev/null +++ b/Python3.10/PyRandLib/baselfib64.py @@ -0,0 +1,206 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.10/PyRandLib/basemelg.py b/Python3.10/PyRandLib/basemelg.py new file mode 100644 index 0000000..5b3faf2 --- /dev/null +++ b/Python3.10/PyRandLib/basemelg.py @@ -0,0 +1,200 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.10/PyRandLib/basemrg.py b/Python3.10/PyRandLib/basemrg.py new file mode 100644 index 0000000..5e05b15 --- /dev/null +++ b/Python3.10/PyRandLib/basemrg.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.10/PyRandLib/basepcg.py b/Python3.10/PyRandLib/basepcg.py new file mode 100644 index 0000000..066e7bb --- /dev/null +++ b/Python3.10/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.10/PyRandLib/baserandom.py b/Python3.10/PyRandLib/baserandom.py new file mode 100644 index 0000000..469fe65 --- /dev/null +++ b/Python3.10/PyRandLib/baserandom.py @@ -0,0 +1,397 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | expovariate(self, lambd) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int, /) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int, /) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def seed(self, _seed: SeedStateType = None, /) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Numerical | tuple[Numerical] | list[Numerical] = 1.0, + /, + times: int = 1 ) -> Numerical | list[Numerical]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64, /) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + hiMask = ((1 << _bitsCount) - 1) ^ (loMask := (1 << (_bitsCount - _rotCount)) - 1) + return ((_value & loMask) << _rotCount) | ((_value & hiMask) >> (_bitsCount - _rotCount)) + + +#===== end of module baserandom.py ===================================== diff --git a/Python3.10/PyRandLib/basesquares.py b/Python3.10/PyRandLib/basesquares.py new file mode 100644 index 0000000..47017c6 --- /dev/null +++ b/Python3.10/PyRandLib/basesquares.py @@ -0,0 +1,161 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.10/PyRandLib/basewell.py b/Python3.10/PyRandLib/basewell.py new file mode 100644 index 0000000..53e052c --- /dev/null +++ b/Python3.10/PyRandLib/basewell.py @@ -0,0 +1,289 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.10/PyRandLib/basexoroshiro.py b/Python3.10/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..81a7514 --- /dev/null +++ b/Python3.10/PyRandLib/basexoroshiro.py @@ -0,0 +1,189 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.10/PyRandLib/cwg128.py b/Python3.10/PyRandLib/cwg128.py new file mode 100644 index 0000000..f5350bc --- /dev/null +++ b/Python3.10/PyRandLib/cwg128.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.10/PyRandLib/cwg128_64.py b/Python3.10/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..67ff0e2 --- /dev/null +++ b/Python3.10/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.10/PyRandLib/cwg64.py b/Python3.10/PyRandLib/cwg64.py new file mode 100644 index 0000000..1ed40b8 --- /dev/null +++ b/Python3.10/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int | float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.10/PyRandLib/fastrand32.py b/Python3.10/PyRandLib/fastrand32.py new file mode 100644 index 0000000..26a25c1 --- /dev/null +++ b/Python3.10/PyRandLib/fastrand32.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.10/PyRandLib/fastrand63.py b/Python3.10/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4e10144 --- /dev/null +++ b/Python3.10/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.10/PyRandLib/lfib116.py b/Python3.10/PyRandLib/lfib116.py new file mode 100644 index 0000000..5083c07 --- /dev/null +++ b/Python3.10/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.10/PyRandLib/lfib1340.py b/Python3.10/PyRandLib/lfib1340.py new file mode 100644 index 0000000..0f4ecdf --- /dev/null +++ b/Python3.10/PyRandLib/lfib1340.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.10/PyRandLib/lfib668.py b/Python3.10/PyRandLib/lfib668.py new file mode 100644 index 0000000..3ae6989 --- /dev/null +++ b/Python3.10/PyRandLib/lfib668.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.10/PyRandLib/lfib78.py b/Python3.10/PyRandLib/lfib78.py new file mode 100644 index 0000000..f2cdeda --- /dev/null +++ b/Python3.10/PyRandLib/lfib78.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.10/PyRandLib/melg19937.py b/Python3.10/PyRandLib/melg19937.py new file mode 100644 index 0000000..10a3332 --- /dev/null +++ b/Python3.10/PyRandLib/melg19937.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.10/PyRandLib/melg44497.py b/Python3.10/PyRandLib/melg44497.py new file mode 100644 index 0000000..5f14081 --- /dev/null +++ b/Python3.10/PyRandLib/melg44497.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.10/PyRandLib/melg607.py b/Python3.10/PyRandLib/melg607.py new file mode 100644 index 0000000..d6c10b6 --- /dev/null +++ b/Python3.10/PyRandLib/melg607.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.10/PyRandLib/mrg1457.py b/Python3.10/PyRandLib/mrg1457.py new file mode 100644 index 0000000..007b8fd --- /dev/null +++ b/Python3.10/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.10/PyRandLib/mrg287.py b/Python3.10/PyRandLib/mrg287.py new file mode 100644 index 0000000..093406d --- /dev/null +++ b/Python3.10/PyRandLib/mrg287.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.10/PyRandLib/mrg49507.py b/Python3.10/PyRandLib/mrg49507.py new file mode 100644 index 0000000..a196c65 --- /dev/null +++ b/Python3.10/PyRandLib/mrg49507.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.10/PyRandLib/pcg1024_32.py b/Python3.10/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..586b6ad --- /dev/null +++ b/Python3.10/PyRandLib/pcg1024_32.py @@ -0,0 +1,281 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState ) + + case 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + case _: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.10/PyRandLib/pcg128_64.py b/Python3.10/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..ddef70e --- /dev/null +++ b/Python3.10/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.10/PyRandLib/pcg64_32.py b/Python3.10/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..4eed457 --- /dev/null +++ b/Python3.10/PyRandLib/pcg64_32.py @@ -0,0 +1,154 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.10/PyRandLib/splitmix.py b/Python3.10/PyRandLib/splitmix.py new file mode 100644 index 0000000..0b97a51 --- /dev/null +++ b/Python3.10/PyRandLib/splitmix.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.10/PyRandLib/squares32.py b/Python3.10/PyRandLib/squares32.py new file mode 100644 index 0000000..268b56e --- /dev/null +++ b/Python3.10/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.10/PyRandLib/squares64.py b/Python3.10/PyRandLib/squares64.py new file mode 100644 index 0000000..5efd5b5 --- /dev/null +++ b/Python3.10/PyRandLib/squares64.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.10/PyRandLib/well1024a.py b/Python3.10/PyRandLib/well1024a.py new file mode 100644 index 0000000..dfd7085 --- /dev/null +++ b/Python3.10/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.10/PyRandLib/well19937c.py b/Python3.10/PyRandLib/well19937c.py new file mode 100644 index 0000000..75ccee7 --- /dev/null +++ b/Python3.10/PyRandLib/well19937c.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 623, 622 + + case 1: + i_1, i_2 = 0, 623 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.10/PyRandLib/well44497b.py b/Python3.10/PyRandLib/well44497b.py new file mode 100644 index 0000000..9a14931 --- /dev/null +++ b/Python3.10/PyRandLib/well44497b.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + match (i := self._index): + case 0: + i_1, i_2 = 1390, 1389 + + case 1: + i_1, i_2 = 0, 1390 + + case _: + i_1, i_2 = i-1, i-2 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.10/PyRandLib/well512a.py b/Python3.10/PyRandLib/well512a.py new file mode 100644 index 0000000..aaf532e --- /dev/null +++ b/Python3.10/PyRandLib/well512a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.10/PyRandLib/xoroshiro1024.py b/Python3.10/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..9a0f7a1 --- /dev/null +++ b/Python3.10/PyRandLib/xoroshiro1024.py @@ -0,0 +1,191 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._index = 0 + self._initstate() + + case 1: + self._index = 0 + self._initstate( _seedState[0] ) + + case _: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.10/PyRandLib/xoroshiro256.py b/Python3.10/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..631b301 --- /dev/null +++ b/Python3.10/PyRandLib/xoroshiro256.py @@ -0,0 +1,180 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.10/PyRandLib/xoroshiro512.py b/Python3.10/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..10d3c0a --- /dev/null +++ b/Python3.10/PyRandLib/xoroshiro512.py @@ -0,0 +1,182 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical | StatesList = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Numerical | StatesList = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + match len( _seedState ): + case 0: + self._initstate() + + case 1: + self._initstate( _seedState[0] ) + + case _: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.10/testCPUPerfs.py b/Python3.10/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.10/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.10/testED.py b/Python3.10/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.10/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= diff --git a/Python3.6/PyRandLib/__init__.py b/Python3.6/PyRandLib/__init__.py new file mode 100644 index 0000000..d5ac68a --- /dev/null +++ b/Python3.6/PyRandLib/__init__.py @@ -0,0 +1,47 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 + + +#===== end of package module __init__.py =============================== diff --git a/Python3.6/PyRandLib/annotation_types.py b/Python3.6/PyRandLib/annotation_types.py new file mode 100644 index 0000000..e097004 --- /dev/null +++ b/Python3.6/PyRandLib/annotation_types.py @@ -0,0 +1,35 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Tuple, Union + +Numerical = Union[ int, float ] +StatesList = Union[ Tuple[int], List[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.6/PyRandLib/basecwg.py b/Python3.6/PyRandLib/basecwg.py new file mode 100644 index 0000000..40c2aa5 --- /dev/null +++ b/Python3.6/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.6/PyRandLib/baselcg.py b/Python3.6/PyRandLib/baselcg.py new file mode 100644 index 0000000..acd6ce2 --- /dev/null +++ b/Python3.6/PyRandLib/baselcg.py @@ -0,0 +1,104 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + +#===== end of module baselcg.py ======================================== diff --git a/Python3.6/PyRandLib/baselfib64.py b/Python3.6/PyRandLib/baselfib64.py new file mode 100644 index 0000000..96da67d --- /dev/null +++ b/Python3.6/PyRandLib/baselfib64.py @@ -0,0 +1,205 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.6/PyRandLib/basemelg.py b/Python3.6/PyRandLib/basemelg.py new file mode 100644 index 0000000..e69665b --- /dev/null +++ b/Python3.6/PyRandLib/basemelg.py @@ -0,0 +1,199 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.6/PyRandLib/basemrg.py b/Python3.6/PyRandLib/basemrg.py new file mode 100644 index 0000000..8059c8e --- /dev/null +++ b/Python3.6/PyRandLib/basemrg.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.6/PyRandLib/basepcg.py b/Python3.6/PyRandLib/basepcg.py new file mode 100644 index 0000000..3cd35f8 --- /dev/null +++ b/Python3.6/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.6/PyRandLib/baserandom.py b/Python3.6/PyRandLib/baserandom.py new file mode 100644 index 0000000..7332741 --- /dev/null +++ b/Python3.6/PyRandLib/baserandom.py @@ -0,0 +1,401 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random +from typing import List, Tuple, Union + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | expovariate(self, lambd) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes( [self.next() >> (self._OUT_BITS - 8) for _ in range(n)] ) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def seed(self, _seed: SeedStateType = None) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Union[Numerical, + Tuple[Numerical], + List[Numerical]] = 1.0, + times: int = 1 ) -> Union[Numerical, List[Numerical]]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + loMask = (1 << (_bitsCount - _rotCount)) - 1 + hiMask = ((1 << _bitsCount) - 1) ^ loMask + hiBits = (_value & hiMask) >> (_bitsCount - _rotCount) + return ((_value & loMask) << _rotCount) | hiBits + + +#===== end of module baserandom.py ===================================== diff --git a/Python3.6/PyRandLib/basesquares.py b/Python3.6/PyRandLib/basesquares.py new file mode 100644 index 0000000..2968237 --- /dev/null +++ b/Python3.6/PyRandLib/basesquares.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + k = int(n * initRand() * self._NORMALIZE) # Notice: _NORMALIZE is defined in base class + h = hexDigits[ k ] + key <<= 4 + key += h + n -= 1 + if k < n: + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.6/PyRandLib/basewell.py b/Python3.6/PyRandLib/basewell.py new file mode 100644 index 0000000..c040bd5 --- /dev/null +++ b/Python3.6/PyRandLib/basewell.py @@ -0,0 +1,288 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: int = 0xda44_2d24 + _a2: int = 0xd3e4_3ffd + _a3: int = 0x8bdc_b91e + _a4: int = 0x86a9_d87e + _a5: int = 0xa8c2_96d1 + _a6: int = 0x5d6b_45cc + _a7: int = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.6/PyRandLib/basexoroshiro.py b/Python3.6/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..447c256 --- /dev/null +++ b/Python3.6/PyRandLib/basexoroshiro.py @@ -0,0 +1,188 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import List, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO: int = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> List[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.6/PyRandLib/cwg128.py b/Python3.6/PyRandLib/cwg128.py new file mode 100644 index 0000000..faff1c7 --- /dev/null +++ b/Python3.6/PyRandLib/cwg128.py @@ -0,0 +1,158 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO: int = (1 << 128) - 1 # Notice: optimization on modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.6/PyRandLib/cwg128_64.py b/Python3.6/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..bd0fbe1 --- /dev/null +++ b/Python3.6/PyRandLib/cwg128_64.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.6/PyRandLib/cwg64.py b/Python3.6/PyRandLib/cwg64.py new file mode 100644 index 0000000..3c1a931 --- /dev/null +++ b/Python3.6/PyRandLib/cwg64.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.6/PyRandLib/fastrand32.py b/Python3.6/PyRandLib/fastrand32.py new file mode 100644 index 0000000..9fa1d0e --- /dev/null +++ b/Python3.6/PyRandLib/fastrand32.py @@ -0,0 +1,123 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix32( _state ) + self._state = initRand() + else: + initRand = SplitMix32() + self._state = initRand() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.6/PyRandLib/fastrand63.py b/Python3.6/PyRandLib/fastrand63.py new file mode 100644 index 0000000..9d8f78b --- /dev/null +++ b/Python3.6/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix63( _state ) + self._state = initRand() + else: + initRand = SplitMix63() + self._state = initRand() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.6/PyRandLib/lfib116.py b/Python3.6/PyRandLib/lfib116.py new file mode 100644 index 0000000..c0b3e63 --- /dev/null +++ b/Python3.6/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k24 = self._index-24 + if k24 < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.6/PyRandLib/lfib1340.py b/Python3.6/PyRandLib/lfib1340.py new file mode 100644 index 0000000..cc0569e --- /dev/null +++ b/Python3.6/PyRandLib/lfib1340.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + k861 = self._index-861 + if k861 < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.6/PyRandLib/lfib668.py b/Python3.6/PyRandLib/lfib668.py new file mode 100644 index 0000000..fa60d74 --- /dev/null +++ b/Python3.6/PyRandLib/lfib668.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + k273 = self._index-273 + if k273 < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.6/PyRandLib/lfib78.py b/Python3.6/PyRandLib/lfib78.py new file mode 100644 index 0000000..a50d034 --- /dev/null +++ b/Python3.6/PyRandLib/lfib78.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + k5 = self._index-5 + if k5 < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.6/PyRandLib/melg19937.py b/Python3.6/PyRandLib/melg19937.py new file mode 100644 index 0000000..49aa010 --- /dev/null +++ b/Python3.6/PyRandLib/melg19937.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 311 + self._index = i_1 + + s311 = self._state[311] + + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s311 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff)) + self._state[311] = s311 + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py diff --git a/Python3.6/PyRandLib/melg44497.py b/Python3.6/PyRandLib/melg44497.py new file mode 100644 index 0000000..ef2a706 --- /dev/null +++ b/Python3.6/PyRandLib/melg44497.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 695 + self._index = i_1 + + s695 = self._state[695] + + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s695 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff)) + self._state[695] = s695 + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.6/PyRandLib/melg607.py b/Python3.6/PyRandLib/melg607.py new file mode 100644 index 0000000..c58f1f7 --- /dev/null +++ b/Python3.6/PyRandLib/melg607.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # Notice: this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + i_1 = (i + 1) % 9 + self._index = i_1 + + s9 = self._state[9] + + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + s9 = ((x >> 1) ^ self._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff)) + self._state[9] = s9 + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.6/PyRandLib/mrg1457.py b/Python3.6/PyRandLib/mrg1457.py new file mode 100644 index 0000000..6ba04f1 --- /dev/null +++ b/Python3.6/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : int = 2_147_483_647 # i.e., 0xffff_ffff or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + k1 = self._index-1 + if k1 < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + k24 = self._index-24 + if k24 < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrg1457.py ======================================== diff --git a/Python3.6/PyRandLib/mrg287.py b/Python3.6/PyRandLib/mrg287.py new file mode 100644 index 0000000..de15971 --- /dev/null +++ b/Python3.6/PyRandLib/mrg287.py @@ -0,0 +1,152 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : int = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + k55 = self._index-55 + if k55 < 0: + k55 += Mrg287._STATE_SIZE + + k119 = self._index-119 + if k119 < 0: + k119 += Mrg287._STATE_SIZE + + k179 = self._index-179 + if k179 < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + myValue = (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % self._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrg287.py ====================================== diff --git a/Python3.6/PyRandLib/mrg49507.py b/Python3.6/PyRandLib/mrg49507.py new file mode 100644 index 0000000..7635f25 --- /dev/null +++ b/Python3.6/PyRandLib/mrg49507.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: int = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : int = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + k7 = self._index-7 + if k7 < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + myValue = (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + +#===== end of module mrg49507.py ======================================= diff --git a/Python3.6/PyRandLib/pcg1024_32.py b/Python3.6/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..38968c8 --- /dev/null +++ b/Python3.6/PyRandLib/pcg1024_32.py @@ -0,0 +1,280 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _EXTENDED_STATE_SIZE: int = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return (self._extendedState[:], self._state) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = self._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + result ^= result >> 22 + + self._extendedState[i] = result + + return result == state & 0b11 + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = cls._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.6/PyRandLib/pcg128_64.py b/Python3.6/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..b1750f4 --- /dev/null +++ b/Python3.6/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : int = (1 << 128) - 1 # notice: optimization on modulo calculations + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * current_state + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + current_state ^= current_state >> 64 # fixed shift XOR is then evaluated + value = current_state & 0xffff_ffff_ffff_ffff + rot_mask = (1 << random_rotation) - 1 + return (value >> random_rotation) | ((value & rot_mask) << (64 - random_rotation)) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.6/PyRandLib/pcg64_32.py b/Python3.6/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..0227493 --- /dev/null +++ b/Python3.6/PyRandLib/pcg64_32.py @@ -0,0 +1,156 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + current_state ^= current_state >> 22 # fixed shift XOR is then evaluated + return (current_state >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = initRand() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.6/PyRandLib/splitmix.py b/Python3.6/PyRandLib/splitmix.py new file mode 100644 index 0000000..00be204 --- /dev/null +++ b/Python3.6/PyRandLib/splitmix.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state = (self._state + 0x9e37_79b9_7f4a_7c15) & 0xffff_ffff_ffff_ffff # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.6/PyRandLib/squares32.py b/Python3.6/PyRandLib/squares32.py new file mode 100644 index 0000000..9a88164 --- /dev/null +++ b/Python3.6/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.6/PyRandLib/squares64.py b/Python3.6/PyRandLib/squares64.py new file mode 100644 index 0000000..93d4c6e --- /dev/null +++ b/Python3.6/PyRandLib/squares64.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.6/PyRandLib/well1024a.py b/Python3.6/PyRandLib/well1024a.py new file mode 100644 index 0000000..3be6baf --- /dev/null +++ b/Python3.6/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ self._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = self._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ self._M3_neg(self._state[(i + 10) & 0x1f], 14) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 11) ^ self._M3_neg(z1, 7) ^ self._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.6/PyRandLib/well19937c.py b/Python3.6/PyRandLib/well19937c.py new file mode 100644 index 0000000..e5ae98d --- /dev/null +++ b/Python3.6/PyRandLib/well19937c.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 623 + else: + i_1, i_2 = 623, 622 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = self._M3_neg(self._state[i], 25) ^ self._M3_pos(self._state[(i + 70) % 624], 27) + z2 = self._M2_pos(self._state[(i + 179) % 624], 19) ^ self._M3_pos(self._state[(i + 449) % 624], 1) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_neg(z1, 9) ^ self._M2_neg(z2, 21) ^ self._M3_pos(z3, 21) + self._index = i_1 + + return self._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.6/PyRandLib/well44497b.py b/Python3.6/PyRandLib/well44497b.py new file mode 100644 index 0000000..5cfcd4b --- /dev/null +++ b/Python3.6/PyRandLib/well44497b.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + if i >= 2: + i_1, i_2 = i - 1, i - 2 + elif i == 1: + i_1, i_2 = 0, 1390 + else: + i_1, i_2 = 1390, 1389 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = self._M3_neg(self._state[i], 24) ^ self._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = self._M3_neg(self._state[(i + 481) % 1391], 10) ^ self._M2_neg(self._state[(i + 229) % 1391], 26) + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = z0 ^ self._M3_pos(z1, 20) ^ self._M6(z2, 9, 14, 5, self._a7) ^ z3 + self._index = i_1 + + return self._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.6/PyRandLib/well512a.py b/Python3.6/PyRandLib/well512a.py new file mode 100644 index 0000000..7bcc043 --- /dev/null +++ b/Python3.6/PyRandLib/well512a.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: int = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = BaseWELL._M3_neg(self._state[i], 16) ^ BaseWELL._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = BaseWELL._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = BaseWELL._M3_neg(z0, 2) ^ BaseWELL._M3_neg(z1, 18) ^ BaseWELL._M2_neg(z2, 28) ^ BaseWELL._M5_neg(z3, 5, BaseWELL._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.6/PyRandLib/xoroshiro1024.py b/Python3.6/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..ffb65f9 --- /dev/null +++ b/Python3.6/PyRandLib/xoroshiro1024.py @@ -0,0 +1,169 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Union + +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + #------------------------------------------------------------------------- + _STATE_SIZE : int = 16 + _SIZE_MODULO: int = 0xf # optimization here, to use operand & + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index += 1 + self._index &= self._SIZE_MODULO + sLow = self._state[ self._index ] + sHigh = self._state[ previousIndex ] ^ sLow + self._state[ previousIndex ] = self._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & self._MODULO) + self._state[ self._index ] = self._rotleft( sHigh, 36 ) + # returns the output value + return (self._rotleft( sLow * 5, 7) * 9) & self._MODULO + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & self._SIZE_MODULO + except: + self._index = 0 + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.6/PyRandLib/xoroshiro256.py b/Python3.6/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..e706c97 --- /dev/null +++ b/Python3.6/PyRandLib/xoroshiro256.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> Tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.6/PyRandLib/xoroshiro512.py b/Python3.6/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..270e628 --- /dev/null +++ b/Python3.6/PyRandLib/xoroshiro512.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: int = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.6/testCPUPerfs.py b/Python3.6/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.6/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.6/testED.py b/Python3.6/testED.py new file mode 100644 index 0000000..2c4232a --- /dev/null +++ b/Python3.6/testED.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029 ) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029 ) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + +#===== end of module testED.py ========================================= diff --git a/Python3.9/PyRandLib/__init__.py b/Python3.9/PyRandLib/__init__.py new file mode 100644 index 0000000..bd75909 --- /dev/null +++ b/Python3.9/PyRandLib/__init__.py @@ -0,0 +1,44 @@ +""" +This file is part of library PyRandLib. +It is provided under MIT License. +Please see files README.md and LICENSE. + +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com +""" + +from .basecwg import BaseCWG +from .baselcg import BaseLCG +from .baselfib64 import BaseLFib64 +from .basemelg import BaseMELG +from .basemrg import BaseMRG +from .baserandom import BaseRandom +from .basesquares import BaseSquares +from .basewell import BaseWELL +from .basexoroshiro import BaseXoroshiro +from .cwg64 import Cwg64 +from .cwg128_64 import Cwg128_64 +from .cwg128 import Cwg128 +from .fastrand32 import FastRand32 +from .fastrand63 import FastRand63 +from .lfib78 import LFib78 +from .lfib116 import LFib116 +from .lfib668 import LFib668 +from .lfib1340 import LFib1340 +from .melg607 import Melg607 +from .melg19937 import Melg19937 +from .melg44497 import Melg44497 +from .mrg287 import Mrg287 +from .mrg1457 import Mrg1457 +from .mrg49507 import Mrg49507 +from .pcg64_32 import Pcg64_32 +from .pcg128_64 import Pcg128_64 +from .pcg1024_32 import Pcg1024_32 +from .squares32 import Squares32 +from .squares64 import Squares64 +from .well512a import Well512a +from .well1024a import Well1024a +from .well19937c import Well19937c +from .well44497b import Well44497b +from .xoroshiro256 import Xoroshiro256 +from .xoroshiro512 import Xoroshiro512 +from .xoroshiro1024 import Xoroshiro1024 diff --git a/Python3.9/PyRandLib/annotation_types.py b/Python3.9/PyRandLib/annotation_types.py new file mode 100644 index 0000000..8514250 --- /dev/null +++ b/Python3.9/PyRandLib/annotation_types.py @@ -0,0 +1,35 @@ +""" +Copyright (c) 2021-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Tuple, Union + +Numerical = Union[ int, float ] +StatesList = Union[ tuple[int], list[int] ] +StatesListAndExt = Tuple[ StatesList, int ] +StateType = Union[ StatesList, StatesListAndExt ] +SeedStateType = Union[ Numerical, StateType ] + + +#===== end of PyRandLib.annotation_types =============================== + +# type: ignore (this comment line is just to avoid boring pylance related error checking) diff --git a/Python3.9/PyRandLib/basecwg.py b/Python3.9/PyRandLib/basecwg.py new file mode 100644 index 0000000..d8b51bf --- /dev/null +++ b/Python3.9/PyRandLib/basecwg.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesListAndExt + + +#============================================================================= +class BaseCWG( BaseRandom ): + """Definition of the base class for all Collatz-Weyl pseudo-random Generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + CWG models are chaotic generators that are combined with Weyl sequences to + eliminate the risk of short cycles. They have a large period, a uniform + distribution, and the ability to generate multiple independent streams by + changing their internal parameters (Weyl increment). CWGs owe their + exceptional quality to the arithmetical dynamics of noninvertible, + generalized, Collatz mappings based on the wellknown Collatz conjecture. + There is no jump function, but each odd number of the Weyl increment + initiates a new unique period, which enables quick initialization of + independent streams (this text is extracted from [8], see README.md). + + The internal implementation of the CWG algorithm varies according to its + implemented version. See implementation classes to get their formal + description. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with low computation time, medium period, 64- bits output values and very + good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = BaseCWG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesListAndExt: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For CWG, this state is defined by a list of control values + (a, weyl and s - or a list of 4 coeffs) and an internal state + value, which are used in methods 'next() and 'setstate() of + every inheriting class. + + All inheriting classes MUST IMPLEMENT this method. + """ + return (self._a, self._weyl, self._s, self._state) + + +#===== end of module basecwg.py ======================================== diff --git a/Python3.9/PyRandLib/baselcg.py b/Python3.9/PyRandLib/baselcg.py new file mode 100644 index 0000000..455c230 --- /dev/null +++ b/Python3.9/PyRandLib/baselcg.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BaseLCG( BaseRandom ): + """Definition of the base class for all LCG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the evaluation + done by Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in + 'TestU01: A C Library for Empirical Testing of Random Number Generators - + ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August + 2007'. It is not recommended to use such pseudo-random numbers generators + for serious simulation applications. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = BaseLCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._state', + which has to be used in methods 'next() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module baselcg.py ======================================== diff --git a/Python3.9/PyRandLib/baselfib64.py b/Python3.9/PyRandLib/baselfib64.py new file mode 100644 index 0000000..774b29a --- /dev/null +++ b/Python3.9/PyRandLib/baselfib64.py @@ -0,0 +1,207 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseLFib64( BaseRandom ): + """The base class for all LFib PRNG based on 64-bits numbers. + + Definition of the base class for all LFib pseudo-random generators based + on 64-bits generated numbers. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^(bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + See LFib78, LFib116, LFib668 and LFib1340 for long period LFib generators (resp. + 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34, 1.2e+201 and + 2.4e+403 periods) while same computation time and far higher precision (64-bits + calculations) than MRGs, but more memory consumption (resp. 17, 55, 607 and 1279 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseLFib() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See LFib78 for an + example. + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFibRand78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFibRand116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFibRand668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFibRand1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an + index in this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module baselfib64.py ===================================== + diff --git a/Python3.9/PyRandLib/basemelg.py b/Python3.9/PyRandLib/basemelg.py new file mode 100644 index 0000000..09647ec --- /dev/null +++ b/Python3.9/PyRandLib/basemelg.py @@ -0,0 +1,201 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMELG( BaseRandom ): + """Definition of the base class for all MELG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: while the WELL algorithm use 32-bits integers as their internal state and + output pseudo-random 32-bits integers also, the MELG algorithm is full 64-bits. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. This is the shortest period version proposed in paper [11]. + See Melg19937 for an even larger period MELG-Generator (2^19,937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMELG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Melg607 for + an example. + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE 64-bits integers, an index in this list and an + additional 64-bits integer as a state extension. Should _seedState + be a sole integer or float then it is used as initial seed for the + random filling of the internal state of the PRNG. Should _seedState + be anything else (e.g. None) then the shuffling of the local + current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE 64-bits integers, an index + in this list and an additional 64-bits integer as a state extension. + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + Should _seedstate not contain a list of self._STATE_SIZE 64- + bits integers, a value for attribute self._index and a value + for attribute self._extState, this method tries its best to + initialize all these values. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + elif count == 2: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemelg.py ======================================= diff --git a/Python3.9/PyRandLib/basemrg.py b/Python3.9/PyRandLib/basemrg.py new file mode 100644 index 0000000..3e9f728 --- /dev/null +++ b/Python3.9/PyRandLib/basemrg.py @@ -0,0 +1,183 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical, SeedStateType, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseMRG( BaseRandom ): + """Definition of the base class for all MRG pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + See Mrg287 for a shor t period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (32-bits modulus) but use of more memory space (1597 + integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseMRG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE' and '_MODULO'. + See Mrg287 for an example. + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() & self._MODULO for _ in range(self._STATE_SIZE) ] + + +#===== end of module basemrg.py ======================================== diff --git a/Python3.9/PyRandLib/basepcg.py b/Python3.9/PyRandLib/basepcg.py new file mode 100644 index 0000000..b07a760 --- /dev/null +++ b/Python3.9/PyRandLib/basepcg.py @@ -0,0 +1,118 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import Numerical + + +#============================================================================= +class BasePCG( BaseRandom ): + """Definition of the base class for all Permuted Congruential Generator pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + See Pcg64_32 for a 2^64 (i.e. 1.84e+19) period PC-Generator with very low + computation time and medium period, with 2 32-bits word integers memory + consumption. Output values are returned on 32 bits. + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = BasePCG() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | -------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Numerical = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> int: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + For LCG, the state is defined with a single integer, 'self._value', + which has to be used in methods 'random() and 'setstate() of every + inheriting class. + """ + return self._state + + +#===== end of module basepcg.py ======================================== diff --git a/Python3.9/PyRandLib/baserandom.py b/Python3.9/PyRandLib/baserandom.py new file mode 100644 index 0000000..97d2545 --- /dev/null +++ b/Python3.9/PyRandLib/baserandom.py @@ -0,0 +1,400 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from random import Random +from typing import Union + +from .annotation_types import Numerical, SeedStateType, StateType + + +#============================================================================= +class BaseRandom( Random ): + """This is the base class for all pseudo-random numbers generators. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + See FastRand32 for a 2^32 (i.e. 4.29e+9) period LC-Generator and FastRand63 for a + 2^63 (i.e. about 9.2e+18) period LC-Generator with very low computation time with + very low memory consumption (resp. 1 and 2 32-bits integers). + + See LFibRand78, LFibRand116, LFibRand668 and LFibRand1340 for large period LFib + generators (resp. 2^78, 2^116, 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, + 8.3e+34, 1.2e+201 and 2.4e+403 periods) while same computation time and far higher + precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and + 1279 32-bits integers). + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 32-bits integers memory consumption. + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (32-bits 47 integers). + See Mrg49507 for a far larger period (2^49507, i.e. 1.2e+14903) with low + computation time too (31-bits modulus) but use of more memory space (1597 + 32-bits integers). + + See Pcg64_32, Pcg128_64 and Pcg1024_32 for medium to very large periods, very low + computation time, and for very low memory consumption for the two first (resp. 4, + 8 and 1,026 times 32-bits). Associated periods are resp. 2^64, 2^128 and 2^32830, + i.e. 1.84e+19, 3.40e+38 and 6.53e+9882. These PRNGs provide multi-streams and jump + ahead features. Since they all are exposing only a part of their internal state, + they are difficult to reverse and to predict. + + See Well512a, Well1024a, Well19937c and Well44479b for large to very large period + generators (resp. 2^512, 2^1024, 2^19937 and 2^44479 periods, i.e. resp. 1.34e+154, + 2.68e+308, 4.32e+6001 and 1.51e+13466 periods), a little bit longer computation + times but very quick escaping from zeroland. Memory consumption is resp. 32, 64, + 624 and 1391 32-bits integers. + + Python built-in class random.Random is subclassed here to use a different basic + generator of our own devising: in that case, overriden methods are: + + random(), seed(), getstate(), and setstate(). + + Since version 2.0 of PyRandLib, the core engine of every PRNG is coded in method + next(). + + Furthermore this class and all its inheriting sub-classes are callable. Example: + rand = BaseRandom() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Please notice that for simulating the roll of a dice you should program: + diceRoll = UFastRandom() + print( int( diceRoll(1, 7) ) ) # prints a uniform roll within {1, ..., 6}. + Such a programming is a simplified while still robust emulation of inherited + methods random.Random.randint(self,1,6) and random.Random.randrange(self,1,7,1). + + Inheriting from random.Random, next methods are also available: + | + | betavariate(self, alpha, beta) + | Beta distribution. + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | Returned values range between 0 and 1. + | + | + | choice(self, seq) + | Choose a random element from a non-empty sequence. + | + | + | expovariate(self, lambd) + | Exponential distribution. + | + | lambd is 1.0 divided by the desired mean. It should be + | nonzero. (The parameter would be called "lambda", but that is + | a reserved word in Python.) Returned values range from 0 to + | positive infinity if lambd is positive, and from negative + | infinity to 0 if lambd is negative. + | + | + | gammavariate(self, alpha, beta) + | Gamma distribution. Not the gamma function! + | + | Conditions on the parameters are alpha > 0 and beta > 0. + | + | + | gauss(self, mu, sigma) + | Gaussian distribution. + | + | mu is the mean, and sigma is the standard deviation. This is + | slightly faster than the normalvariate() function. + | + | Not thread-safe without a lock around calls. + | + | + | getrandbits(self, k) + | Returns a non-negative Python integer with k random bits. + | Changed since version 3.9: This method now accepts zero for k. + | + | + | getstate(self) + | Return internal state; can be passed to setstate() later. + | + | + | lognormvariate(self, mu, sigma) + | Log normal distribution. + | + | If you take the natural logarithm of this distribution, you'll get a + | normal distribution with mean mu and standard deviation sigma. + | mu can have any value, and sigma must be greater than zero. + | + | + | normalvariate(self, mu, sigma) + | Normal distribution. + | + | mu is the mean, and sigma is the standard deviation. + | + | + | paretovariate(self, alpha) + | Pareto distribution. alpha is the shape parameter. + | + | + | randbytes(self, n) + | Generate n random bytes. + | This method should not be used for generating security tokens. + | Notice: this method has been added in Python 3.9. It is implemented + | in PyRandLib for former versions of the language also. + | + | + | randint(self, a, b) + | Return random integer in range [a, b], including both end points. + | + | + | randrange(self, start, stop=None, step=1, int=) + | Choose a random item from range(start, stop[, step]). + | + | This fixes the problem with randint() which includes the + | endpoint; in Python this is usually not what you want. + | + | Do not supply the 'int' argument. + | + | + | sample(self, population, k) + | Chooses k unique random elements from a population sequence or set. + | + | Returns a new list containing elements from the population while + | leaving the original population unchanged. The resulting list is + | in selection order so that all sub-slices will also be valid random + | samples. This allows raffle winners (the sample) to be partitioned + | into grand prize and second place winners (the subslices). + | + | Members of the population need not be hashable or unique. If the + | population contains repeats, then each occurrence is a possible + | selection in the sample. + | + | To choose a sample in a range of integers, use range as an argument. + | This is especially fast and space efficient for sampling from a + | large population: sample(range(10000000), 60) + | + | + | seed(self, a=None, version=2) + | Initialize internal state from hashable object. + | + | None or no argument seeds from current time or from an operating + | system specific randomness source if available. + | + | For version 2 (the default), all of the bits are used if *a *is a str, + | bytes, or bytearray. For version 1, the hash() of *a* is used instead. + | + | If *a* is an int, all bits are used. + | + | + | setstate(self, state) + | Restore internal state from object returned by getstate(). + | + | + | shuffle(self, x, random=None, int=) + | x, random=random.random -> shuffle list x in place; return None. + | + | Optional arg random is a 0-argument function returning a random + | float in [0.0, 1.0); by default, the standard random.random. + | + | + | triangular(self, low=0.0, high=1.0, mode=None) + | Triangular distribution. + | + | Continuous distribution bounded by given lower and upper limits, + | and having a given mode value in-between. + | + | http://en.wikipedia.org/wiki/Triangular_distribution + | + | + | uniform(self, a, b) + | Get a random number in the range [a, b) or [a, b] depending on rounding. + | + | + | vonmisesvariate(self, mu, kappa) + | Circular data distribution. + | + | mu is the mean angle, expressed in radians between 0 and 2*pi, and + | kappa is the concentration parameter, which must be greater than or + | equal to zero. If kappa is equal to zero, this distribution reduces + | to a uniform random angle over the range 0 to 2*pi. + | + | + | weibullvariate(self, alpha, beta) + | Weibull distribution. + | + | alpha is the scale parameter and beta is the shape parameter. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 2.328_306_436_538_696_289_062_5e-10 # i.e. 1.0 / (1 << 32) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 32 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not a number then the local time is used + (with its shuffled value) as a seed. + + Notice: the Python built-in base class random.Random internally + calls method setstate() which MUST be overridden in classes that + inherit from class BaseRandom. + """ + super().__init__( _seed ) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + + This is the core of the PRNGs. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def random(self) -> float: + """Returns the next pseudo-random floating-point number in interval [0.0, 1.0). + + Inheriting classes MUST OVERRIDE the value of the base class constant + attribute _NORMALIZE when the random integer values they return are + coded on anything else than 32 bits. + """ + return self.next() * self._NORMALIZE + + + #------------------------------------------------------------------------- + def getrandbits(self, k: int, /) -> int: + """Returns k bits from the internal state of the generator. + + k must be a positive value greater or equal to zero. + """ + assert k >= 0, "the returned bits count must not be negative" + assert k < self._OUT_BITS, f"the returned bits count must be less than {self._OUT_BITS}" + + return 0 if k == 0 else self.next() >> (self._OUT_BITS - k) + + + #------------------------------------------------------------------------- + def randbytes(self, n: int, /) -> bytes: + """Generates n random bytes. + + This method should not be used for generating security tokens. + (use Python built-in secrets.token_bytes() instead) + """ + assert n >= 0 # and self._OUT_BITS >= 8 + return bytes([self.next() >> (self._OUT_BITS - 8) for _ in range(n)]) + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can then be passed to setstate() to restore the state. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def setstate(self, _state: StateType, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call to getstate(), + and setstate() restores the internal state of the generator to what + it was at the time setstate() was called. + Inheriting classes MUST IMPLEMENT this method. + """ + raise NotImplementedError() + + + #------------------------------------------------------------------------- + def seed(self, _seed: SeedStateType = None, /) -> None: + """Initiates the internal state of this pseudo-random generator. + """ + try: + self.setstate( _seed ) + except: + super().seed( _seed ) + + + #------------------------------------------------------------------------- + def __call__(self, _max : Union[Numerical, + tuple[Numerical], + list[Numerical]] = 1.0, + /, + times: int = 1 ) -> Union[Numerical, list[Numerical]]: + """This class's instances are callable. + + The returned value is uniformly contained within the + interval [0.0 : _max]. When times is set, a list of + iterated pseudo-random values is returned. 'times' + must be an integer. If less than 1 it is forced to + be 1. + '_max' may be a list or a tuple of values, in which + case a list of related pseudo-random values is + returned with entries of the same type than the same + indexed entry in '_max'. + """ + assert isinstance( times, int ) + if times < 1: + times = 1 + + if isinstance( _max, int ): + ret = [ int(_max * self.random()) for _ in range(times) ] + elif isinstance( _max, float ): + ret = [ _max * self.random() for _ in range(times) ] + else: + try: + if times == 1: + ret = [ self(m,1) for m in _max ] + else: + ret = [ [self(m,1) for m in _max] for _ in range(times) ] + except: + ret = [ self.__call__(times=1) ] + + return ret[0] if len(ret) == 1 else ret + + + #------------------------------------------------------------------------- + @classmethod + def _rotleft(cls, _value: int, _rotCount: int, _bitsCount: int = 64, /) -> int: + """Returns the value of a left rotating by _rotCount bits + + Useful for some inheriting classes. + """ + #assert 1 <=_rotCount <= _bitsCount + hiMask = ((1 << _bitsCount) - 1) ^ (loMask := (1 << (_bitsCount - _rotCount)) - 1) + return ((_value & loMask) << _rotCount) | ((_value & hiMask) >> (_bitsCount - _rotCount)) + + +#===== end of module baserandom.py ===================================== diff --git a/Python3.9/PyRandLib/basesquares.py b/Python3.9/PyRandLib/basesquares.py new file mode 100644 index 0000000..47017c6 --- /dev/null +++ b/Python3.9/PyRandLib/basesquares.py @@ -0,0 +1,161 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StatesList +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseSquares( BaseRandom ): + """Definition of the base class for the Squares counter-based pseudo-random Generator. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Squares models are based on an incremented counter and a key. The + algorithm squares a combination of the counter and the key values, + and exchanges the upper and lower bits of the combination, the + whole repeated a number of times (4 to 5 rounds). Output values + are provided on 32-bits or on 64-bits according to the model. See + [9] in README.md. + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. Caution: the 64-bits version + should not pass the birthday test, which is a randmoness issue, + while this is not mentionned in the original paper (see [9] in + file README.md). + + Furthermore this class is callable: + rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def getstate(self) -> StatesList: + """Returns an object capturing the current internal state of the generator. + """ + return (self._counter, self._key) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType, /) -> None: + """Restores or sets the internal state of the generator. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._counter = 0 + self._key = self._initKey( _state ) + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + self._counter = 0 + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._key = self._initKey( int(_state + 0.5) & 0xffff_ffff_ffff_ffff ) + else: + self._key = self._initKey( int(_state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff ) + + else: + try: + self._counter = _state[0] & 0xffff_ffff_ffff_ffff + self._key = (_state[1] & 0xffff_ffff_ffff_ffff) | 1 # Notice: key must be odd + except: + # uses local time as initial seed + self._counter = 0 + self._key = self._initKey() + + + #------------------------------------------------------------------------- + def _initKey(self, _seed: int = None, /) -> int: + """Initalizes the attribute _key according to the original recommendations - see [9]. + """ + hexDigits = [ i for i in range(1, 16) ] + key = 0 + + initRand = SplitMix32( _seed ) + + # 8 high hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + # 8 low hexa digits - all different + n = 15 + while n >= 8: + h = hexDigits[ (k := int(n * initRand() * self._NORMALIZE)) ] # Notice: _NORMALIZE is defined in base class + key <<= 4 + key += h + if k < (n := n-1): + hexDigits[ k ] = hexDigits[ n ] + hexDigits[ n ] = h + + return key | 1 # Notice: key must be odd + + +#===== end of module basesquares.py ==================================== diff --git a/Python3.9/PyRandLib/basewell.py b/Python3.9/PyRandLib/basewell.py new file mode 100644 index 0000000..968a29c --- /dev/null +++ b/Python3.9/PyRandLib/basewell.py @@ -0,0 +1,290 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baserandom import BaseRandom +from .annotation_types import SeedStateType, StateType +from .splitmix import SplitMix32 + + +#============================================================================= +class BaseWELL( BaseRandom ): + """Definition of the base class for all WELL pseudo-random generators. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in the 4 different versions implemented here has been coded + here as a direct implementation of their descriptions in the initial paper + "Improved Long-Period Generators Based on Linear Recurrences Modulo 2", François + PANNETON and Pierre L’ECUYER (Université de Montréal) and Makoto MATSUMOTO + (Hiroshima University), in ACM Transactions on Mathematical Software, Vol. 32, + No. 1, March 2006, Pages 1–16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + So, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory little consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 15.1e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseWell() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attributes '_STATE_SIZE'. See Well512a for + an example. + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937b (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497c | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937b generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range(0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for the + random filling of the internal list of self._STATE_SIZE integers. + Should _seedState be anything else (e.g. None) then the shuffling + of the local current time value is used as such an initial seed. + + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. It is a + tuple containing a list of self._STATE_SIZE integers and an index in + this list (index value being then in range(0,self._STATE_SIZE). + """ + return (self._state[:], self._index) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: StateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[0][:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int(_index) % self._STATE_SIZE + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: StateType = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed and the value+1 of the modulo. + initRand = SplitMix32( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + + #------------------------------------------------------------------------- + @classmethod + def _M0(cls, x: int = None, /) -> int: + return 0 + + #------------------------------------------------------------------------- + @classmethod + def _M1(cls, x: int, /) -> int: + return x + + #------------------------------------------------------------------------- + @classmethod + def _M2_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x >> t + + #------------------------------------------------------------------------- + @classmethod + def _M2_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return (x << t) & 0xffff_ffff + + #------------------------------------------------------------------------- + @classmethod + def _M3_pos(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ (x >> t) + + #------------------------------------------------------------------------- + @classmethod + def _M3_neg(cls, x: int, t: int, /) -> int: + #assert 0 <= t < 32 + return x ^ ((x << t) & 0xffff_ffff) + + #------------------------------------------------------------------------- + @classmethod + def _M4(cls, x: int, a: int, /) -> int: + #assert 0 <= a <= 0xffff_ffff + return (x >> 1) ^ a if x & 0x8000_0000 else x >> 1 + + #------------------------------------------------------------------------- + @classmethod + def _M5_pos(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= b <= 0xffff_ffff + return x ^ ((x >> t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M5_neg(cls, x: int, t: int, a: int, /) -> int: + #assert 0 <= t < 32 + #assert 0 <= a <= 0xffff_ffff + return x ^ ((x << t) & a) + + #------------------------------------------------------------------------- + @classmethod + def _M6(cls, x: int, q: int, t: int, s: int, a: int, /) -> int: + #assert 0 <= q < 32 + #assert 0 <= t < 32 + #assert 0 <= s < 32 + #assert 0 <= a <= 0xffff_ffff + y = (((x << q) & 0xffff_ffff) ^ (x >> (32 - q))) & cls._d(s) + return (y ^ a) if (x & (1< int: + #assert 0 <= s < 32 + return 0xffff_ffff ^ (1 << s) + + #------------------------------------------------------------------------- + @classmethod + def _tempering(cls, x: int, b: int, c: int, /) -> int: + #assert 0 <= b <= 0xffff_ffff + #assert 0 <= c <= 0xffff_ffff + # notice: the generic algorithm truncs x on w-bits. All of the implemented + # ones in PyRandLib are set on 32-bits. So, no truncation takes place here + x = x ^ (((x << 7) & 0xffff_ffff) & b) + return x ^ (((x << 15) & 0xffff_ffff) & c) + + #------------------------------------------------------------------------- + # definition of algorithm constants + _a1: Final[int] = 0xda44_2d24 + _a2: Final[int] = 0xd3e4_3ffd + _a3: Final[int] = 0x8bdc_b91e + _a4: Final[int] = 0x86a9_d87e + _a5: Final[int] = 0xa8c2_96d1 + _a6: Final[int] = 0x5d6b_45cc + _a7: Final[int] = 0xb729_fcec + + +#===== end of module basewell.py ======================================= diff --git a/Python3.9/PyRandLib/basexoroshiro.py b/Python3.9/PyRandLib/basexoroshiro.py new file mode 100644 index 0000000..2fab10a --- /dev/null +++ b/Python3.9/PyRandLib/basexoroshiro.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, Union + +from .baserandom import BaseRandom +from .annotation_types import Numerical, StatesList, StateType +from .splitmix import SplitMix64 + + +#============================================================================= +class BaseXoroshiro( BaseRandom ): + """The base class for all xoroshiro PRNGs. + + Definitiion of the base class for all versions of the xoroshiro algorithm + implemented in PyRandLib. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The xoroshiro algorithm is a version of the Scrambled Linear Pseudorandom Number + Generators. The xoroshiro linear transformation updates cyclically two words of a + larger state array. The base xoroshiro linear transformation is obtained combining + a rotation, a shift, and again a rotation. + (extracted from the original paper, see [10] in file README.md) + + An addition or a multiplication operation is internally applied also to the state + of the PRNGs. Doubling the same operation has proven to enhance then randomness + quality of the PRNG. This is the model of the algorithms that is implemeted in + PyRandLib. + + The implemented algorithms shortly escape from the zeroland (10 to 100 calls are + enough to get equiprobability of bits 0 and 1 on 4 successive calls). The 256 + version of the algorithm has nevertheless shown close repeats flaws, with a bad + Hamming weight near zero. Xoroshiro512 seems to best fit this property. + (see https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + See Xoroshiro256, Xoroshiro512, Xoroshiro1024 for long period generators (resp. + 2^256, 2^512 and 2^1024 periods, i.e. resp. 1.16e+77, 1.34e+154 and 1.80e+308 + periods), 64-bits precision calculations and short memory consumption (resp. 8, + 16 and 32 integers coded on 64 bits. + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = BaseXoroshiro() # Caution: this is just used as illustrative. This base class cannot be instantiated + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Inheriting classes have to define class attribute '_STATE_SIZE'. See Xoroshiro1024 + for an example. + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float ]= 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 64) - 1 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def getstate(self) -> list[int]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == self._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(self._STATE_SIZE) ] + + +#===== end of module basexoroshiro.py ================================== + diff --git a/Python3.9/PyRandLib/cwg128.py b/Python3.9/PyRandLib/cwg128.py new file mode 100644 index 0000000..f5350bc --- /dev/null +++ b/Python3.9/PyRandLib/cwg128.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 128-bits output values with large + period (min 2^135, i.e. 4.36e+40) but short computation time. All CWG + algorithms offer multi streams features, by simply using different initial + settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 96 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + Notice: in the original paper, four control value c[0] to c[3] are used. + It appears that these value are used just are 's' for c[0], 'x' for c[1], + 'a' for c[2] and 'weyl' for c[3] in the other versions of the algorithm. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 2.938_735_877_055_718_769_921_8e-39 # i.e. 1.0 / (1 << 128) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 128 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + _MODULO: Final[int] = (1 << 128) - 1 # notice: optimization on modulo computations + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & self._MODULO + self._weyl = (self._weyl + self._s) & self._MODULO + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & self._MODULO + # returns the xored-shifted output value + return self._state ^ (self._a >> 96) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._state = (initRand() << 64) | initRand() # Notice: in the original paper, this seems to be erroneously initialized on sole 64 lowest bits + self._s = (initRand() << 64) | initRand() | 1 # Notice: s must be odd + + else: + try: + self._a = _state[0] & self._MODULO + self._weyl = _state[1] & self._MODULO + self._s = (_state[2] & self._MODULO) | 1 # Notice: s must be odd + self._state = _state[3] & self._MODULO + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128.py ========================================= diff --git a/Python3.9/PyRandLib/cwg128_64.py b/Python3.9/PyRandLib/cwg128_64.py new file mode 100644 index 0000000..1ca37b4 --- /dev/null +++ b/Python3.9/PyRandLib/cwg128_64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg128_64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 128-bits calculations and 64-bits output values with small + period (min 2^71, i.e. 2.36e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) | 1) * ((a += x(i)) >> 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg64 for a minimum 2^70 (i.e. about 1.18e+21) period CW-Generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state | 1) * (self._a >> 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + # returns the xored-shifted output value + return (self._state ^ (self._a >> 48)) & 0xffff_ffff_ffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # Notice: must be odd + self._state = _state[3] & ((1 << 128) - 1) # Notice: coded on 128 bits + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg128_64.py ====================================== diff --git a/Python3.9/PyRandLib/cwg64.py b/Python3.9/PyRandLib/cwg64.py new file mode 100644 index 0000000..1d52085 --- /dev/null +++ b/Python3.9/PyRandLib/cwg64.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basecwg import BaseCWG +from .annotation_types import SeedStateType +from .splitmix import SplitMix64 + + +#============================================================================= +class Cwg64( BaseCWG ): + """ + Pseudo-random numbers generator - Collatz-Weyl pseudo-random Generators + dedicated to 64-bits calculations and 64-bits output values with small + period (min 2^70, i.e. 1.18e+21) but short computation time. All CWG + algorithms offer multi streams features, by simply using different + initial settings for control value 's' - see below. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This CWG model evaluates pseudo-random numbers suites x(i) as a simple + mathematical function of + + x(i+1) = (x(i) >> 1) * ((a += x(i)) | 1) ^ (weyl += s) + + and returns as the output value the xored shifted: a >> 48 ^ x(i+1) + + where a, weyl and s are the control values and x the internal state of the + PRNG. 's' must be initally odd. 'a', 'weyl' and initial state 'x' may be + initialized each with any 64-bits value. + + See Cwg128_64 for a minimum 2^71 (i.e. about 2.36e+21) period CW-Generator + with very low computation time, medium period, 64-bits output values and + very good randomness characteristics. + See Cwg128 for a minimum 2^135 (i.e. about 4.36e+40) period CW-generator + with very low computation time, medium period, 64- bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Cwg64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the CWGs that have + been implemented in PyRandLib, as presented in paper [8] - see file README.md. + + | PyRandLib class | [8] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Cwg64 | CWG64 | 8 x 4-bytes | >= 2^70 | n.a. | n.a. | 0 | 0 | 0 | + | Cwg128_64 | CWG128_64 | 10 x 4-bytes | >= 2^71 | n.a. | n.a. | 0 | 0 | 0 |_ + | Cwg128 | CWG128 | 16 x 4-bytes | >= 2^135 | n.a. | n.a. | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._a = (self._a + self._state) & 0xffff_ffff_ffff_ffff + self._weyl = (self._weyl + self._s) & 0xffff_ffff_ffff_ffff + self._state = (((self._state >> 1) * (self._a | 1)) ^ self._weyl) & 0xffff_ffff_ffff_ffff + # returns the xored-shifted output value + return self._state ^ (self._a >> 48) + + + #------------------------------------------------------------------------- + def setstate(self, _state: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. If None, the local system time + is used instead. + """ + if _state is None or isinstance(_state, int) or isinstance(_state, float): + initRand = SplitMix64( _state ) + self._a = self._weyl = 0 + self._s = initRand() | 1; # Notice: must be odd + self._state = (initRand() << 64) | initRand() # Notice: coded on 128 bits + + else: + try: + self._a = _state[0] & 0xffff_ffff_ffff_ffff + self._weyl = _state[1] & 0xffff_ffff_ffff_ffff + self._s = (_state[2] & 0xffff_ffff_ffff_ffff) | 1 # notice: s must be odd + self._state = _state[3] & 0xffff_ffff_ffff_ffff + + except: + # uses local time as initial seed + self.setstate() + + +#===== end of module cwg64.py ========================================== diff --git a/Python3.9/PyRandLib/fastrand32.py b/Python3.9/PyRandLib/fastrand32.py new file mode 100644 index 0000000..26a25c1 --- /dev/null +++ b/Python3.9/PyRandLib/fastrand32.py @@ -0,0 +1,121 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix32 + + +#============================================================================= +class FastRand32( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 32-bits calculations with very short period (about 4.3e+09) but very + short time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 32-bits model is based on (a=69069, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^32. + + See FastRand63 for a 2^63 (i.e. about 9.2e+18) period LC-Generator with + low computation time also, longer period and quite better randomness + characteristics than for FastRand32. + + Furthermore this class is callable: + rand = FastRand32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x1_0dcd * self._state + 1) & 0xffff_ffff + return self._state + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix32( _state )() + else: + self._state = SplitMix32()() + + +#===== end of module fastrand32.py ===================================== diff --git a/Python3.9/PyRandLib/fastrand63.py b/Python3.9/PyRandLib/fastrand63.py new file mode 100644 index 0000000..4e10144 --- /dev/null +++ b/Python3.9/PyRandLib/fastrand63.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselcg import BaseLCG +from .annotation_types import Numerical +from .splitmix import SplitMix63 + + +#============================================================================= +class FastRand63( BaseLCG ): + """ + Pseudo-random numbers generator - Linear Congruential Generator dedicated + to 63-bits calculations with very short period (about 9.2e+18) and short + time computation. + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + LCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i-1): x(i) = (a*x(i-1) + c) mod m + + Results are nevertheless considered to be poor as stated in the + evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in 'TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, + pp.22-40, August 2007'. It is not recommended to use such pseudo-random + numbers generators for serious simulation applications. + + The implementation of this LCG 63-bits model is based on (a=9219741426499971445, c=1) + since these two values have evaluated to be the 'best' ones for LCGs + within TestU01 while m = 2^63. + + See FastRand32 for a 2^32 (i.e. 4.3e+9) period LC-Generator with very low + computation time but shorter period and worse randomness characteristics + than for FastRand63. + + Furthermore this class is callable: + rand = FastRand63() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = FastRand63() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LCGs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ---------------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | FastRand32 | LCG(2^32, 69069, 1) | 1 x 4-bytes | 2^32 | 3.20 | 0.67 | 11 | 106 | *too many* | + | FastRand63 | LCG(2^63, 9219741426499971445, 1) | 2 x 4-bytes | 2^63 | 4.20 | 0.75 | 0 | 5 | 7 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 1.084_202_172_485_504_434_007_453e-19 # i.e. 1.0 / (1 << 63) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 63 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + self._state = (0x7ff3_19fa_a77b_e975 * self._state + 1) & 0x7fff_ffff_ffff_ffff + return self._state + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance(_state, int) or isinstance(_state, float): + self._state = SplitMix63( _state )() + else: + self._state = SplitMix63()() + + +#===== end of module fastrand63.py ===================================== diff --git a/Python3.9/PyRandLib/lfib116.py b/Python3.9/PyRandLib/lfib116.py new file mode 100644 index 0000000..5083c07 --- /dev/null +++ b/Python3.9/PyRandLib/lfib116.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib116( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (8.3e+34). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-24) + x(i-55)) mod 2^64 + + and offers a period of about 2^116 - i.e. 8.3e+34 - with low computation time due + to the use of a 2^64 modulo and little memory space consumption (55 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib668 and LFib1340 for long period LFib generators (resp. 2^78, + 2^668 and 2^1340 periods, i.e. resp. 3.0e+23, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib116() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib116() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 55 # this 'LFib(2^64, 55, 24, +)' generator is based on a suite containing 55 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + if (k24 := self._index-24) < 0: + k24 += LFib116._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k24] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib116._STATE_SIZE + + return myValue + + +#===== end of module lfib116.py ======================================== diff --git a/Python3.9/PyRandLib/lfib1340.py b/Python3.9/PyRandLib/lfib1340.py new file mode 100644 index 0000000..0f4ecdf --- /dev/null +++ b/Python3.9/PyRandLib/lfib1340.py @@ -0,0 +1,136 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib1340( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (2.4e+403). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = (x(i-861) + x(i-1279)) mod 2^64 + + and offers a period of about 2^1340 - i.e. 2.4e+403 - with low computation time + due to the use of a 2^64 modulo but memory space consumption (1379 long integ- + ers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib668 for long period LFib generators (resp. 2^78, + 2^116 and 2^668 periods, i.e. resp. 3.0e+23, 8.3e+34 and 1.2e+201 periods) while + same computation time and far higher precision (64-bits calculations) than MRGs, + but memory consumption (resp. 17, 55 and 607 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib1340() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib1340() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1279 # this 'LFib(2^64, 1279, 861, +)' generator is based on a suite containing 1279 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-861 and i-1279 -th values + + if (k861 := self._index-861) < 0: + k861 += LFib1340._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k861] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib1340._STATE_SIZE + + return myValue + + +#===== end of module lfib1340.py ====================================== diff --git a/Python3.9/PyRandLib/lfib668.py b/Python3.9/PyRandLib/lfib668.py new file mode 100644 index 0000000..3ae6989 --- /dev/null +++ b/Python3.9/PyRandLib/lfib668.py @@ -0,0 +1,135 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib668( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (1.2e+201). + + This module is part of library PyRandLib. + + Copyright (c) 2017-2025 Philippe Schmouker + + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-273) + x(i-607) ) mod 2^64 + + and offers a period of about 2^668 - i.e. 1.2e+201 - with low computation time due + to the use of a 2^64 modulo but memory space consumption (607 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib78, LFib116 and LFib1340 for long period LFib generators (resp. 2^78, + 2^116 and 2^1340 periods, i.e. resp. 3.0e+23, 8.3e+34 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 17, 55 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib668() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib668() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 607 # this 'LFib(2^64, 607, 273, +)' generator is based on a suite containing 607 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-273 and i-607 -th values + + if (k273 := self._index-273) < 0: + k273 += LFib668._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k273] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index+1) % LFib668._STATE_SIZE + + return myValue + + +#===== end of module lfib668.py ======================================= diff --git a/Python3.9/PyRandLib/lfib78.py b/Python3.9/PyRandLib/lfib78.py new file mode 100644 index 0000000..f2cdeda --- /dev/null +++ b/Python3.9/PyRandLib/lfib78.py @@ -0,0 +1,134 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .baselfib64 import BaseLFib64 + + +#============================================================================= +class LFib78( BaseLFib64 ): + """ + Pseudo-random numbers generator - Definition of a fast 64-bits Lagged Fibonacci + Generator with quite short period (3.0e+23). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be: + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) in "TestU01: A C Library for Empirical Testing of Random Number + Generators - ACM Transactions on Mathematical Software, vol.33 n.4, pp.22-40, + August 2007". It is recommended to use such pseudo-random numbers generators + rather than LCG ones for serious simulation applications. + + The implementation of this LFib 64-bits model is based on a Lagged Fibonacci + generator (LFIB) that uses the recurrence + + x(i) = ( x(i-5) + x(i-17) ) mod 2^64 + + and offers a period of about 2^78 - i.e. 3.0e+23 - with low computation time due + to the use of a 2^64 modulo and few memory space consumption (17 long integers). + + Please notice that the TestUO1 article states that the operator should be '*' + while Mascagni & Srinivasan in their original article stated that the operator is + '+'. We've implemented here the original operator: '+'. + + See LFib116, LFib668 and LFib1340 for long period LFib generators (resp. 2^116, + 2^668 and 2^1340 periods, i.e. resp. 8.3e+34, 1.2e+201 and 2.4e+403 periods) + while same computation time and far higher precision (64-bits calculations) than + MRGs, but memory consumption (resp. 55, 607 and 1279 integers). + + Please notice that this class and all its inheriting sub-classes are callable. + Example: + + rand = LFib78() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = LFib78() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the LFibs that have + been implemented in PyRandLib, as provided in paper "TestU01, ..." - see + file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------------ | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | LFib78 | LFib(2^64, 17, 5, +) | 34 x 4-bytes | 2^78 | n.a. | 1.1 | 0 | 0 | 0 | + | LFib116 | LFib(2^64, 55, 24, +) | 110 x 4-bytes | 2^116 | n.a. | 1.0 | 0 | 0 | 0 | + | LFib668 | LFib(2^64, 607, 273, +) | 1,214 x 4-bytes | 2^668 | n.a. | 0.9 | 0 | 0 | 0 | + | LFib1340 | LFib(2^64, 1279, 861, +) | 2,558 x 4-bytes | 2^1340 | n.a. | 0.9 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 17 # this 'LFib(2^64, 17, 5, +)' generator is based on a suite containing 17 integers + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-5 and i-17 -th values + + if (k5 := self._index - 5) < 0: + k5 += LFib78._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k5] + self._state[self._index]) & 0xffff_ffff_ffff_ffff) + + # next index + self._index = (self._index + 1) % LFib78._STATE_SIZE + + return myValue + + +#===== end of module lfib78.py ========================================= diff --git a/Python3.9/PyRandLib/melg19937.py b/Python3.9/PyRandLib/melg19937.py new file mode 100644 index 0000000..10a3332 --- /dev/null +++ b/Python3.9/PyRandLib/melg19937.py @@ -0,0 +1,120 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg19937( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^19,937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg19937() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg19937() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 312 + _A_COND = (0, 0x5c32_e06d_f730_fc42) # this tuple will avoid an 'if' in method 'next()' + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 311) + + s311 = self._state[311] + x = (self._state[i] & 0xffff_fffe_0000_0000) | (self._state[i_1] & 0x0000_0001_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[311] = (s311 := ((x >> 1) ^ Melg19937._A_COND[x & 0x01]) ^ self._state[(i+81) % 311] ^ (s311 ^ ((s311 << 23) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s311 ^ (s311 >> 33)) + return (si ^ ((si << 16) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 19) % 311]) & 0x6aed_e6fd_97b3_38ec) + + +#===== end of module melg19937.py ====================================== diff --git a/Python3.9/PyRandLib/melg44497.py b/Python3.9/PyRandLib/melg44497.py new file mode 100644 index 0000000..5f14081 --- /dev/null +++ b/Python3.9/PyRandLib/melg44497.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg44497( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg607 for a large period MELG-Generator (2^607, i.e. 5.31e+182) with medium + computation time and the equivalent of 21 32-bits integers memory little + consumption. + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + + Furthermore, this class is callable: + rand = Melg444907() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg444907() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 696 + _A_COND = (0, 0x4fa9_ca36_f293_c9a9) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 695) + + s695 = self._state[695] + x = (self._state[i] & 0xffff_8000_0000_0000) | (self._state[i_1] & 0x0000_7fff_ffff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[695] = (s695 := ((x >> 1) ^ Melg44497._A_COND[x & 0x01]) ^ self._state[(i+373) % 695] ^ (s695 ^ ((s695 << 37) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s695 ^ (s695 >> 14)) + return (si ^ ((si << 6) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 95) % 695]) & 0x06fb_bee2_9aae_fd91) + + +#===== end of module melg44977.py ====================================== diff --git a/Python3.9/PyRandLib/melg607.py b/Python3.9/PyRandLib/melg607.py new file mode 100644 index 0000000..d6c10b6 --- /dev/null +++ b/Python3.9/PyRandLib/melg607.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemelg import BaseMELG + + +#============================================================================= +class Melg607( BaseMELG ): + """Pseudo-random numbers generator. Definition of a 64-bits Maximally Equidistrib- + uted Long-period Linear generator with a large period (2^607, i.e. 5.31e+182). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Maximally Equidistributed Long-period Linear Generators (MELG) use linear + recurrence based on state transitions with double feedbacks and linear output + transformations with several memory references. See reference [11] in README.md. + + MELGs offer large to very large periods with best known results in the evaluation + of their randomness. They ensure a maximally equidistributed generation of pseudo + random numbers. They pass all TestU01 tests and newer ones but are the slowest to + compute ones in the base of PRNGs that have been implemented in PyRandLib. + + Notice: the implementation of this version of the MELG algorithm in PyRandLib is + not as optimized as it is in C code provided by MELG authors. It is rather derived + from the formal description and related tables provided in paper referenced [11] + in file README.md, to be able to easier validate the Python code here. + + Notice also: in the original paper [11], in the description of Algorithm 1, an + error (typo) appears at the initialization of 'x'. An bit-xor operation appears + in the text while it should be a bit-or operation as explaind in plain text. We + correct in in the code here. + + See Melg19937 for an even larger period MELG-Generator (2^19937, i.e. 4.32e+6001), + same computation time and equivalent of 626 integers memory consumption. + See Melg44497 for a very large period (2^44,497, i.e. 15.1e+13,466) with similar + computation time but use of even more memory space (equivalent of 1,393 32-bits + integers). This is the longest period version proposed in paper [11]. + + Furthermore, this class is callable: + rand = Melg607() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Melg607() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MELG algorithms that + have been implemented in PyRandLib, as provided in paper [11] and when available. + + | PyRandLib class | [11] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Melg607 | melg607-64 | 21 x 4-bytes | 2^607 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Melg19937 | melg19937-64 | 625 x 4-bytes | 2^19937 | n.a. | 4.21 | 0 | 0 | 0 | + | Melg44497 | melg44497-64 | 1,393 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 10 # the internal state of this PRNG is set on ten 64-bits integers N=10 + _A_COND = (0, 0x81f1_fd68_0123_48bc) # this tuple will avoid an 'if' in method 'next()', a=0x81f1... + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Notice: the output value is coded on 64-bits. + """ + i = self._index + self._index = (i_1 := (i+1) % 9) + + s9 = self._state[9] + x = (self._state[i] & 0xffff_ffff_8000_0000) | (self._state[i_1] & 0x0000_0000_7fff_ffff) # notice: | instead of ^ as erroneously printed in [11] + self._state[9] = (s9 := ((x >> 1) ^ Melg607._A_COND[x & 0x01]) ^ self._state[(i+5) % 9] ^ (s9 ^ ((s9 << 13) & 0xffff_ffff_ffff_ffff))) + + si = self._state[i] = x ^ (s9 ^ (s9 >> 35)) + return (si ^ ((si << 30) & 0xffff_ffff_ffff_ffff)) ^ ((self._state[(i + 3) % 9]) & 0x66ed_c62a_6bf8_c826) + + +#===== end of module melg607.py ======================================== diff --git a/Python3.9/PyRandLib/mrg1457.py b/Python3.9/PyRandLib/mrg1457.py new file mode 100644 index 0000000..007b8fd --- /dev/null +++ b/Python3.9/PyRandLib/mrg1457.py @@ -0,0 +1,149 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg1457( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with long period (3.98e+438). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random + generator proposed by Deng and Lin. The DX-47-3 version uses the recurrence + + x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + and offers a period of about 2^1457 - i.e. nearly 4.0e+438 - with low computation + time. + + See Mrg287 for a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1597 + integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg1457() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 47 # this 'DX-47-3' MRG is based on a suite containing 47 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fff_ffff, or (1<<31)-1, the modulo for DX-47-3 MRG + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The DX-47-3 version uses the recurrence + # x(i) = (2^26+2^19) * (x(i-1) + x(i-24) + x(i-47)) mod (2^31-1) + + # evaluates indexes in suite for the i-1, i-24 (and i-47) -th values + if (k1 := self._index - 1) < 0: + k1 = Mrg1457._STATE_SIZE - 1 + + if (k24 := self._index - 24) < 0: + k24 += Mrg1457._STATE_SIZE + + # then evaluates current value + myValue = (0x0408_0000 * (self._state[k1] + self._state[k24] + self._state[self._index]) ) % 2_147_483_647 + self._state[self._index] = myValue + + # next index + self._index = (self._index + 1) % Mrg1457._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand1457.py ==================================== diff --git a/Python3.9/PyRandLib/mrg287.py b/Python3.9/PyRandLib/mrg287.py new file mode 100644 index 0000000..093406d --- /dev/null +++ b/Python3.9/PyRandLib/mrg287.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg287( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 32-bits Multiple Recursive + Generator with a long period (2.49e+86). + + This module is part of library PyRandLib. + + Copyright (c) 2016-2025 Philippe Schmouker + + Multiple Recursive Generators (MRGs) use recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 32-bits model is based on a Lagged Fibonacci + generator (LFIB), the Marsa-LFIB4 one. + Lagged Fibonacci generators LFib( m, r, k, op) use the recurrence + + x(i) = (x(i-r) op (x(i-k)) mod m + + where op is an operation that can be + + (addition), + - (substraction), + * (multiplication), + ^ (bitwise exclusive-or). + + With the + or - operation, such generators are in fact MRGs. They offer very large + periods with the best known results in the evaluation of their randomness, as + stated in the evaluation done by Pierre L'Ecuyer and Richard Simard (Universite de + Montreal) paper. + + The Marsa-LIBF4 version uses the recurrence + + x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + and offers a period of about 2^287 - i.e. 2.49e+86 - with low computation time due + to the use of a 2^32 modulo. + + See Mrg1457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + See Mrg49507 for a far longer period (2^49507, i.e. 1.2e+14903) with lower + computation time too (31-bits modulus) but use of more memory space (1_597 + integers). + + Furthermore, this class is callable: + rand = Mrg287() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg287() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 256 # this 'Marsa-LFIB4' MRG is based on a suite containing 256 integers + _MODULO : Final[int] = 0xffff_ffff + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # The Marsa-LIBF4 version uses the recurrence + # x(i) = (x(i-55) + x(i-119) + x(i-179) + x(i-256)) mod 2^32 + + # evaluates indexes in suite for the i-55, i-119, i-179 (and i-256) -th values + if (k55 := self._index-55) < 0: + k55 += Mrg287._STATE_SIZE + + if (k119 := self._index-119) < 0: + k119 += Mrg287._STATE_SIZE + + if (k179 := self._index-179) < 0: + k179 += Mrg287._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (self._state[k55] + self._state[k119] + self._state[k179] + self._state[self._index]) & 0xffff_ffff) + + # next index + self._index = (self._index+1) % Mrg287._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand287.py ================================== diff --git a/Python3.9/PyRandLib/mrg49507.py b/Python3.9/PyRandLib/mrg49507.py new file mode 100644 index 0000000..a196c65 --- /dev/null +++ b/Python3.9/PyRandLib/mrg49507.py @@ -0,0 +1,140 @@ +""" +Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basemrg import BaseMRG + + +#============================================================================= +class Mrg49507( BaseMRG ): + """ + Pseudo-random numbers generator - Definition of a fast 31-bits Multiple Recursive + Generator with a very long period (1.17e+14_903). + + This module is part of library PyRandLib. + + Multiple Recursive Generators (MRGs) uses recurrence to evaluate pseudo-random + numbers suites. Recurrence is of the form: + + x(i) = A * SUM[ x(i-k) ] mod M + + for 2 to more k different values. + + MRGs offer very large periods with the best known results in the evaluation of + their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + + The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG. It + uses the recurrence + + x(i) = (-2^25-2^7) * (x(i-7) + x(i-1597)) mod (2^31-1) + + and offers a period of about 2^49_507 - i.e. nearly 1.2e+14_903 - with low + computation time. + + See Mrg287 fo r a short period MR-Generator (2^287, i.e. 2.49e+86) with low + computation time but 256 integers memory consumption. + See Mrg457 for a longer period MR-Generator (2^1457, i.e. 4.0e+438) and longer + computation time (2^31-1 modulus calculations) but less memory space consumption + (47 integers). + + Class random.Random is sub-subclassed here to use a different basic generator of + our own devising: in that case, overriden methods are: + random(), seed(), getstate(), and setstate(). + + Furthermore this class is callable: + rand = Mrg49507() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Mrg49507() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the + inherited methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the MRGs that have been + implemented in PyRandLib, as provided in paper "TestU01, ..." - see file README.md. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49507 | n.a. | 1.4 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _STATE_SIZE: Final[int] = 1597 # this 'DX-1597-2-7' MRG is based on a suite containing 1597 integers + _MODULO : Final[int] = 2_147_483_647 # i.e. 0x7fffffff, or (1<<31)-1, the modulo for DX-1597-2-7 MRG + + _NORMALIZE: Final[float] = 4.656_612_873_077_039_257_8e-10 # i.e. 1.0 / (1 << 31) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 31 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates indexes in suite for the i-7, i-1597 -th values + if (k7 := self._index - 7) < 0: + k7 += Mrg49507._STATE_SIZE + + # then evaluates current value + self._state[self._index] = (myValue := (-67_108_992 * (self._state[k7] + self._state[self._index])) % 2_147_483_647) + + # next index + self._index = (self._index+1) % Mrg49507._STATE_SIZE + + # then returns the integer generated value + return myValue + + +#===== end of module mrgrand49507.py =================================== diff --git a/Python3.9/PyRandLib/pcg1024_32.py b/Python3.9/PyRandLib/pcg1024_32.py new file mode 100644 index 0000000..a6df012 --- /dev/null +++ b/Python3.9/PyRandLib/pcg1024_32.py @@ -0,0 +1,282 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .annotation_types import Numerical, SeedStateType, StateType +from .pcg64_32 import Pcg64_32 +from .splitmix import SplitMix32 + + +#============================================================================= +class Pcg1024_32( Pcg64_32 ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + extended by 1,024 equistributed generators, dedicated to 64-bits + calculations and 32-bits output with very large period (about 6.53e+9882) + but very short time computation. This version of the PCG algorithm gets + the largest memory consumption: 1,026 x 4-bytes. The PCG algorithm offers + jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg1024_32 class implements the "PCG XSH RS 64/32 (EXT 1024)" version + of th e PCG algorithm, as specified in the related paper (see [7] in + document README.md), so with a and c coded on 64-bits, the modulo m = 2^64 + and the additional permutation output function and its internal multiple + states that implements its 1024-dimensionally equidistributed generator + directly coded in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg1024_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg1024_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constants + _EXTENDED_STATE_SIZE: Final[int] = 1024 + + + #------------------------------------------------------------------------- + def __init__(self, _seed: SeedStateType = None, /) -> None: + """Constructor. + + Should _seed be None or not be of SeedStateType then the + local time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attributes self._state and self._extendedState and sets them + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates a to-be-xor'ed 32-bits value from current extended state + if self._state & 0xffff_ffff == 0: + self._advancetable() + extendedValue = self._extendedState[ self._state & 0x03ff ] + + # then xor's it with the next 32-bits value evaluated with the internal state + return super().next() ^ extendedValue + + + #------------------------------------------------------------------------- + def getstate(self) -> StateType: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a list that contains self._STATE_SIZE integers. + """ + return [ self._extendedState[:], self._state ] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers (31-bits) and an index in this list + (index value being then in range(0,self._STATE_SIZE)). Should + _seedState be a sole integer or float then it is used as + initial seed for the random filling of the internal list of + self._STATE_SIZE integers. Should _seedState be anything else + (e.g. None) then the shuffling of the local current time + value is used as such an initial seed. + """ + try: + count = len( _seedState ) + + if count == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState ) + + elif count == 2: + # each entry in _seedState[0] MUST be a 32-bits integer + # _seedState[1] MUST be a 64-bits integer + extendedCount = len( _seedState[0] ) + if extendedCount == Pcg1024_32._EXTENDED_STATE_SIZE: + self._extendedState = _seedState[0][:] + self._state = _seedState[1] + elif extendedCount > 0: + extended = _seedState[0] + for s in _seedState[1:]: + extended ^= s + self._initextendedstate( extended ) + self._state = _seedState[1] + else: + self._initstate( _seedState[1] ) + + else: + self._initstate() + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _advancetable(self) -> None: + """Advances the extended states + """ + carry = False + for i, s in enumerate( self._extendedState ): + if carry: + carry = self._extendedstep(s, i) + if self._extendedstep(s, i): # notice: must be evaluated before carry is set + carry = True + + + #------------------------------------------------------------------------- + def _extendedstep(self, value: int, i: int, /) -> bool: + """Evaluates new extended state indexed value in the extended state table. + + Returns True when the evaluated extended value is set to zero on all bits + but its two lowest ones - these two bits never change with MCGs. + """ + state = (0xacb8_6d69 * (value ^ (value >> 22))) & 0xffff_ffff + state = Pcg1024_32._invxrs( state, 32, 4 + (state >> 28) & 0x0f ) + state = (0x108e_f2d9 * state + 2 * (i + 1)) & 0xffff_ffff + + result = 0x108e_f2d9 * (state ^ (state >> (4 + (state >> 28)))) + + self._extendedState[i] = (result := result ^ (result >> 22)) + + return result == (state & 0b11) + + + #------------------------------------------------------------------------- + def _initextendedstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the extended list of values. + + Inits the extended list of values according to some initial + seed that has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + # feeds the list according to an initial seed. + initRand = SplitMix32( _initialSeed ) + self._extendedState = [ initRand() for _ in range(Pcg1024_32._EXTENDED_STATE_SIZE) ] + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal state of this PRNG. + + Inits its current state and its extended state also. The + initial seed has to be an integer, or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as the initial seed value. + The same initial seed is finally used to seed the current + state and the extended state. To avoid any unexpected + correlation between current state and any value of the + extended one, we use different PRNGs to seed the internal + state on one side and the extended state on the other side. + """ + super().setstate( _initialSeed ) # uses Pcg64_32() + self._initextendedstate( _initialSeed ) # uses Well1024a() + + + #------------------------------------------------------------------------- + @classmethod + def _invxrs(cls, value: int, bitsCount: int, shift: int, /) -> int: + """Evaluates the inversion of an xor-shift operation. + """ + if shift * 2 >= bitsCount: + return value ^ (value >> shift) + + botMask = (1 << (bitsCount - shift * 2)) - 1 + topMask = ~botMask & 0xffff_ffff + + top = (value ^ (value >> shift)) & topMask + + newBitsShift = bitsCount - shift + bot = Pcg1024_32._invxrs( (top | (value & botMask)) & ((1 << newBitsShift) - 1), newBitsShift, shift ) & botMask + + return top | bot + + +#===== end of module pcg1024_32.py ===================================== diff --git a/Python3.9/PyRandLib/pcg128_64.py b/Python3.9/PyRandLib/pcg128_64.py new file mode 100644 index 0000000..ddef70e --- /dev/null +++ b/Python3.9/PyRandLib/pcg128_64.py @@ -0,0 +1,175 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg128_64( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 128-bits calculations and 64-bits output with medium period + (about 3.40e+38) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg128_64 class implements the "PCG XSL RR 128/64 (LCG)" version of + the PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a and c values coded on 128 bits (see method next()), + the modulo m = 2^128 and the additional permutation output function + directly implemented in method 'next()'. + + See Pcg64_32 for a 2^64 (i.e. about 1.84e+19) period PC-Generator with low + computation time also and a longer period than for Pcg64_32, with 2 + 32-bits word integers memory consumption. Output values are returned on + 32 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg128_64() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg128_64() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: Final[float] = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: Final[int] = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + _MODULO_128 : Final[int] = (1 << 128) - 1 # optimization here to get modulo via operator & + + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + self._state = (0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645 * (current_state := self._state) + 0x5851_F42D_4C95_7F2D_1405_7B7E_F767_814F) & Pcg128_64._MODULO_128 + # the permutated output is then computed + random_rotation = current_state >> 122 # random right rotation is set with the 6 upper bits of internal state + value = (current_state ^ (current_state >> 64)) & 0xffff_ffff_ffff_ffff + return (value >> random_rotation) | ((value & ((1 << random_rotation) - 1))) << (64 - random_rotation) + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & Pcg128_64._MODULO_128 + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & Pcg128_64._MODULO_128 + else: + self._state = int( _state * (Pcg128_64._MODULO_128 + 1)) & Pcg128_64._MODULO_128 + + else: + # uses local time as initial seed + initRand = SplitMix64() + self._state = (initRand() << 64) | initRand() + + +#===== end of module pcg128_64.py ====================================== diff --git a/Python3.9/PyRandLib/pcg64_32.py b/Python3.9/PyRandLib/pcg64_32.py new file mode 100644 index 0000000..4eed457 --- /dev/null +++ b/Python3.9/PyRandLib/pcg64_32.py @@ -0,0 +1,154 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basepcg import BasePCG +from .annotation_types import Numerical +from .splitmix import SplitMix64 + + +#============================================================================= +class Pcg64_32( BasePCG ): + """ + Pseudo-random numbers generator - Permutated Congruential Generator + dedicated to 64-bits calculations and 32-bits output with medium period + (about 1.84e+19) but very short time computation. The PCG algorithm + offers jump ahead and multi streams features. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + PCG models evaluate pseudo-random numbers suites x(i) as a simple mathem- + atical function of + + x(i) = (a * x(i-1) + c) mod m + + as are LCGs, but associated with a permutation of a subpart of the bits of + the internal state of the PRNG. The output of PCGs is this permutated + subpart of its internal state, leading to a very large enhancement of the + randomness of these algorithms compared with the LCGs one. + + These PRNGs have been tested with TestU01 and have shown to pass all tests + (Pierre L'Ecuyer and Richard Simard (Universite de Montreal) in 'TestU01: + A C Library for Empirical Testing of Random Number Generators - ACM + Transactions on Mathematical Software, vol.33 n.4, pp.22-40, August 2007') + + PCGs are very fast generators, with low memory usage except for a very few + of them and medium to very large periods. They offer jump ahead and multi + streams features for most of them. They are difficult to very difficult to + invert and to predict. + + The Pcg64_32 class implements the "PCG XSH RS 64/32 (LCG)" version of the + PCG algorithm, as specified in the related paper (see [7] in document + README.md), so with a = 6364136223846793005, c = 1442695040888963407, the + modulo m = 2^64 and the additional permutation output function directly + implemented in method 'next()'. + + See Pcg128_64 for a 2^128 (i.e. about 3.40e+38) period PC-Generator with + low computation time also and a longer period than for Pcg64_32, with 4 + 32-bits word integers memory consumption. Output values are returned on + 64 bits. + + See Pcg1024_32 for a 2^32,830 (i.e. about 6.53e+9882) period PC-Generator + with low computation time also and a very large period, but 1,026 32-bits + word integers memory consumption. Output values are returned on 32 bits. + + Furthermore this class is callable: + rand = Pcg64_32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Pcg64_32() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Reminder: + We give you here below a copy of the table of tests for the PCGs that have + been implemented in PyRandLib, as provided by the author of PCGs - see + reference [7] in file README.md. + + | PyRandLib class | initial PCG algo name | Memory Usage | Period | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | -------- | ------------ | ---------------- | ----------- | -------------- | + | Pcg64_32 | PCG XSH RS 64/32 (LCG) | 2 x 4-bytes | 2^64 | 0.79 | 0 | 0 | 0 | + | Pcg128_64 | PCG XSL RR 128/64 (LCG) | 4 x 4-bytes | 2^128 | 1.70 | 0 | 0 | 0 | + | Pcg1024_32 | PCG XSH RS 64/32 (EXT 1024) | 1,026 x 4-bytes | 2^32,830 | 0.78 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None or not a numerical then the local + time is used (with its shuffled value) as a seed. + """ + super().__init__( _seed ) # this call creates attribute self._state and sets it + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + # evaluates next internal state + current_state = self._state + self._state = (0x5851_F42D_4C95_7F2D * current_state + 0x1405_7B7E_F767_814F) & 0xffff_ffff_ffff_ffff + # the permutated output is then computed + random_shift = (current_state >> 61) & 0x07 # random shift is set with the 3 upper bits of internal state + return ((current_state ^ (current_state >> 22)) >> (22 + random_shift)) & 0xffff_ffff + + + #------------------------------------------------------------------------- + def setstate(self, _state: Numerical, /) -> None: + """Restores the internal state of the generator. + + _state should have been obtained from a previous call + to getstate(), and setstate() restores the internal + state of the generator to what it was at the time + setstate() was called. + """ + if isinstance( _state, int ): + # passed initial seed is an integer, just uses it + self._state = _state & 0xffff_ffff_ffff_ffff + + elif isinstance( _state, float ): + # transforms passed initial seed from float to integer + if _state < 0.0 : + _state = -_state + if _state >= 1.0: + self._state = int( _state + 0.5 ) & 0xffff_ffff_ffff_ffff + else: + self._state = int( _state * 0x1_0000_0000_0000_0000) & 0xffff_ffff_ffff_ffff + + else: + # uses local time as initial seed + self._state = SplitMix64()() + + +#===== end of module pcg64_32.py ======================================= diff --git a/Python3.9/PyRandLib/splitmix.py b/Python3.9/PyRandLib/splitmix.py new file mode 100644 index 0000000..0b97a51 --- /dev/null +++ b/Python3.9/PyRandLib/splitmix.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import time + +from .annotation_types import Numerical + + +#============================================================================= +class SplitMix64: + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 64 bits. It implements the + 64-bits version of the Fast Splittable Pseudorandom Number + Generators proposed by Steele Jr, Guy L., Doug Lea, and Christine + H. Flood in "Fast splittable pseudorandom number generators.", in + ACM SIGPLAN Notices 49.10 (2014): pp. 453-472. + + It uses the Gamma method inited by Sebastiano Vigna (vigna@acm.org) + in 2015, provided under the Creative Commons license and modified + under the same license by D. Lemire by Aug. 2017. + (see https://github.com/lemire/testingRNG/blob/master/source/splitmix64.h). + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + if isinstance( _seed, int ): + self.state = self.__call__( _seed ) + + elif isinstance( _seed, float ): + # transforms passed initial seed from float to integer + if _seed < 0.0 : + _seed = -_seed + + if _seed >= 1.0: + self._state = self.__call__( round(_seed) ) + else: + self._state = self.__call__( int(_seed * 0x1_0000_0000_0000_0000) ) + + else: + # uses system local time + self._state = self.__call__( int(time.time() * 1000.0) ) + + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + if _seed is not None: + self._state = _seed & 0xffff_ffff_ffff_ffff + + self._state += 0x9e37_79b9_7f4a_7c15 # this is the 'Golden' Gamma value: int( ((1+math.sqrt(5))/2) * 2**64) & 0xffff_ffff_ffff_ffff + self._state &= 0xffff_ffff_ffff_ffff + + z = self._state + z = ((z ^ (z >> 30)) * 0xbf5_8476_d1ce_4e5b9) & 0xffff_ffff_ffff_ffff + z = ((z ^ (z >> 27)) * 0x94d_049b_b133_111eb) & 0xffff_ffff_ffff_ffff + + return z ^ (z >> 31) + + +#============================================================================= +class SplitMix63( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 63 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 63 higher bits generated by base class operator () + return super().__call__( _seed ) >> 1 + + +#============================================================================= +class SplitMix32( SplitMix64 ): + """The splitting and mixing algorithm used to initialize internal states of PRNGs. + + This class and its inheriting classes are only provided for the + initialization of the internal state of all other PRNGs. It SHOULD + NOT BE USED as a generic PRNG due to is randomness big limitations. + + This class evaluates "random" values on 32 bits. + """ + #------------------------------------------------------------------------- + def __init__(self, _seed: Numerical = None, /) -> None: + """Constructor. + + Should _seed be None, the internal system local time is used as the initial seed + """ + super().__init__( _seed ) + + #------------------------------------------------------------------------- + def __call__(self, _seed: int = None, /) -> int: + """The split-mix algorithm. + """ + # returns the 32 higher bits generated by base class operator () + return super().__call__( _seed ) >> 32 + + +#===== end of module splitmix.py ======================================= diff --git a/Python3.9/PyRandLib/squares32.py b/Python3.9/PyRandLib/squares32.py new file mode 100644 index 0000000..b58d642 --- /dev/null +++ b/Python3.9/PyRandLib/squares32.py @@ -0,0 +1,111 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares32( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + + See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 64-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Return a 32-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + return ((x * x + z) & 0xffff_ffff_ffff_ffff) >> 32 + + +#===== end of module squares32.py ====================================== diff --git a/Python3.9/PyRandLib/squares64.py b/Python3.9/PyRandLib/squares64.py new file mode 100644 index 0000000..3d38a45 --- /dev/null +++ b/Python3.9/PyRandLib/squares64.py @@ -0,0 +1,133 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from .basesquares import BaseSquares +from .annotation_types import SeedStateType, StatesList + + +#============================================================================= +class Squares64( BaseSquares ): + """ + Pseudo-random numbers generator - Squares pseudo-random Generators + dedicated to 64-bits calculations and 32-bits output values with + small period (min 2^64, i.e. 1.84e+19) but short computation time. + All Squares algorithms offer multi streams features, by simply + using different initial settings for control value 'key'. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + This Squares models is based on a four rounds of squaring and + exchanging of upper and lower bits of the successive combinations. + Output values are provided on 32-bits or on 64-bits according to + the model. See [9] in README.md. + Caution: this 64-bits output values version should not pass the + birthday test, which is a randmoness issue, while this is not + mentionned in the original paper (see [9] in file README.md). + + See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with + low computation time, medium period, 32-bits output values and + very good randomness characteristics. + + Furthermore this class is callable: + rand = Squares32() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the Squares + that have been implemented in PyRandLib, as presented in paper [9] + - see file README.md. + + | PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 | + | Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_ + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + + #------------------------------------------------------------------------- + _NORMALIZE: float = 5.421_010_862_427_522_170_037_3e-20 # i.e. 1.0 / (1 << 64) + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. It is THE multiplier constant value to be applied to + pseudo-random number for them to be normalized in interval [0.0, 1.0). + """ + + _OUT_BITS: int = 64 + """The value of this class attribute MUST BE OVERRIDDEN in inheriting + classes if returned random integer values are coded on anything else + than 32 bits. + """ + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: SeedStateType = None, /) -> None: + """Constructor. + + Should _seedState be None then the local time is used as a seed (with + its shuffled value). + Notice: method setstate() is not implemented in base class BaseRandom. + So, it must be implemented in classes inheriting BaseLCG and it must + initialize attribute self._state. + """ + super().__init__( _seedState ) # this internally calls 'setstate()' which + # MUST be implemented in inheriting classes + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + + Returns a 64-bits value. + """ + self._counter = (self._counter + 1) & 0xffff_ffff_ffff_ffff + + y = x = (self._counter * self._key) & 0xffff_ffff_ffff_ffff + z = (y + self._key) & 0xffff_ffff_ffff_ffff + # round 1 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 2 + x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 3 + x = (x * x + y) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 4 + t = x = (x * x + z) & 0xffff_ffff_ffff_ffff + x = (x >> 32) | ((x & 0xffff_ffff) << 32) + # round 5 + return t ^ (((x * x + y) >> 32) & 0xffff_ffff) + + +#===== end of module squares64.py ====================================== diff --git a/Python3.9/PyRandLib/well1024a.py b/Python3.9/PyRandLib/well1024a.py new file mode 100644 index 0000000..dfd7085 --- /dev/null +++ b/Python3.9/PyRandLib/well1024a.py @@ -0,0 +1,138 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well1024a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^1024, i.e. 2.68e+308). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well1024a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well1024a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 32 # this Well1024a PRNG internal state is based on a suite containing 32 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i_1 = ((i := self._index) - 1) & 0x1f + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._state[i] ^ BaseWELL._M3_pos(self._state[(i + 3) & 0x1f], 8) + # notice: the transformation applied to self._state[i] for Well1024a + # is the identity which leads to simplification also + z2 = BaseWELL._M3_neg(self._state[(i + 24) & 0x1f], 19) ^ BaseWELL._M3_neg(self._state[(i + 10) & 0x1f], 14) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = BaseWELL._M3_neg(z0, 11) ^ BaseWELL._M3_neg(z1, 7) ^ BaseWELL._M3_neg(z2, 13) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well1024a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + + self._index = i_1 + return z3 + + +#===== end of module well1024a.py ====================================== diff --git a/Python3.9/PyRandLib/well19937c.py b/Python3.9/PyRandLib/well19937c.py new file mode 100644 index 0000000..a89344f --- /dev/null +++ b/Python3.9/PyRandLib/well19937c.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well19937c( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^19937, i.e. 4.32e+6001). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well19937c() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well19937c() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 624 # this Well19937c PRNG internal state is based on a suite containing 624 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + if (i := self._index) >= 2: + i_1, i_2 = i-1, i-2 + elif i == 1: + i_1, i_2 = 0, 623 + else: + i_1, i_2 = 623, 622 + + z0 = (self._state[i_1] & 0x0000_0001) ^ (self._state[i_2] & 0xffff_fffe) + z1 = BaseWELL._M3_neg(self._state[i], 25) ^ BaseWELL._M3_pos(self._state[(i + 70) % 624], 27) + z2 = BaseWELL._M2_pos(self._state[(i + 179) % 624], 19) ^ BaseWELL._M3_pos(self._state[(i + 449) % 624], 1) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_neg(z1, 9) ^ BaseWELL._M2_neg(z2, 21) ^ BaseWELL._M3_pos(z3, 21) + + self._index = i_1 + return BaseWELL._tempering(z3, 0xe46e1700, 0x9b868000) + + +#===== end of module well19937c.py ===================================== diff --git a/Python3.9/PyRandLib/well44497b.py b/Python3.9/PyRandLib/well44497b.py new file mode 100644 index 0000000..68ac9db --- /dev/null +++ b/Python3.9/PyRandLib/well44497b.py @@ -0,0 +1,137 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well44497b( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^44497, i.e. 1.51e+13466). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well1024a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well44497b() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well44497b() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 1391 # this Well44497b PRNG internal state is based on a suite containing 1391 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + if (i := self._index) >= 2: + i_1, i_2 = i-1, i-2 + elif i == 1: + i_1, i_2 = 0, 1390 + else: + i_1, i_2 = 1390, 1389 + + z0 = (self._state[i_1] & 0x0001_ffff) ^ (self._state[i_2] & 0xfffe_0000) + z1 = BaseWELL._M3_neg(self._state[i], 24) ^ BaseWELL._M3_pos(self._state[(i + 23) % 1391], 30) + z2 = BaseWELL._M3_neg(self._state[(i + 481) % 1391], 10) ^ BaseWELL._M2_neg(self._state[(i + 229) % 1391], 26) + + self._state[i] = (z3 := z1 ^ z2) + self._state[i_1] = z0 ^ BaseWELL._M3_pos(z1, 20) ^ BaseWELL._M6(z2, 9, 14, 5, BaseWELL._a7) ^ z3 + + self._index = i_1 + return BaseWELL._tempering(z3, 0x93dd1400, 0xfa118000) + + +#===== end of module Well44497b.py ===================================== diff --git a/Python3.9/PyRandLib/well512a.py b/Python3.9/PyRandLib/well512a.py new file mode 100644 index 0000000..aaf532e --- /dev/null +++ b/Python3.9/PyRandLib/well512a.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final + +from .basewell import BaseWELL + + +#============================================================================= +class Well512a( BaseWELL ): + """ + Pseudo-random numbers generator. Definition of a fast 32-bits Well-Equidistributed + Long-period Linear generator with a large period (2^512, i.e. 1.34e+154). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + Well-Equidistributed Long-period Linear Generators (WELL) use linear recurrence + based on primitive characteristic polynomials associated with left- and right- + shifts and xor operations to fastly evaluate pseudo-random numbers suites. + + WELLs offer large to very large periods with best known results in the evaluation + of their randomness, as stated in the evaluation done by Pierre L'Ecuyer and + Richard Simard (Universite de Montreal) in "TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical Software, + vol.33 n.4, pp.22-40, August 2007". It is recommended to use such pseudo-random + numbers generators rather than LCG ones for serious simulation applications. + Furthermore, WELLs have proven their great ability to very fastly escape from + zeroland. + + Notice: the algorithm in its Well512a version has been coded here as a direct + implementation of its description in the initial paper: "Improved Long-Period + Generators Based on Linear Recurrences Modulo 2", François PANNETON and Pierre + L'ECUYER (Université de Montréal) and Makoto MATSUMOTO (Hiroshima University), in + ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, Pages 1-16. + (see https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + As such, only minimalist optimization has been coded, with the aim at easing the + verification of its proper implementation. + + See Well512a for a large period WELL-Generator (2^512, i.e. 1.34e+154) with low + computation time and 16 integers memory consumption. + See Well1024a for a longer period WELL-Generator (2^1024, i.e. 2.68e+308), same + computation time and 32 integers memory consumption. + See Well199937b for a far longer period (2^19937, i.e. 4.32e+6001) with similar + computation time but use of more memory space (624 integers). + See Well44497c for a very large period (2^44497, i.e. 1.51e+13466) with similar + computation time but use of even more memory space (1,391 integers). + + Furthermore, this class is callable: + rand = Well512a() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Notice that for simulating the roll of a dice you should program: + diceRoll = Well512a() + print( int(diceRoll.randint(1, 6)) ) # prints a uniform roll within set {1, 2, 3, 4, 5, 6} + + + Such a programming is an accelerated while still robust emulation of the inherited + methods: + - random.Random.randint(self,1,6) and + - random.Random.randrange(self,1,7,1) + + Reminder: + We give you here below a copy of the table of tests for the WELL algorithms that + have been implemented in PyRandLib, as provided in paper "TestU01, ..." and when + available. + + | PyRandLib class | TU01 generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | ------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Well512a | not available | 16 x 4-bytes | 2^512 | n.a. | n.a. | n.a. | n.a. | n.a. | + | Well1024a | WELL1024a | 32 x 4-bytes | 2^1024 | 4.0 | 1.1 | 0 | 4 | 4 | + | Well19937c (1) | WELL19937a | 624 x 4-bytes | 2^19937 | 4.3 | 1.3 | 0 | 2 | 2 | + | Well44497b | not available | 1,391 x 4-bytes | 2^44497 | n.a. | n.a. | n.a. | n.a. | n.a. | + + (1)The Well19937c generator provided with library PyRandLib implements the + Well19937a algorithm augmented with an associated tempering algorithm. + This should very slightly slow down its CPU performance while enhancing + its pseudo-randomness quality, as measured by TestU01. + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + # 'protected' constant + _STATE_SIZE: Final[int] = 16 # this Well512a PRNG internal state is based on a suite containing 16 integers (32-bits wide each) + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. + """ + i = self._index + i_1 = (i - 1) & 0xf + + z0 = self._state[i_1] + # notice: all blocks of bits in the internal state are 32 bits wide, which leads to a great + # simplification for the implementation of the generic WELL algorithm when evaluating z0. + z1 = self._M3_neg(self._state[i], 16) ^ self._M3_neg(self._state[(i + 13) & 0x0f], 15) + z2 = self._M3_pos(self._state[(i + 9) & 0x0f], 11) + # notice: the last term of the above equation in the WELL generic algorithm is, for its Well512a + # version, the zero matrix _M0 which we suppress here for calculations optimization purpose + z3 = z1 ^ z2 + + self._state[i] = z3 + self._state[i_1] = self._M3_neg(z0, 2) ^ self._M3_neg(z1, 18) ^ self._M2_neg(z2, 28) ^ self._M5_neg(z3, 5, self._a1) + + self._index = i_1 + return z3 + + +#===== end of module well512a.py ======================================= diff --git a/Python3.9/PyRandLib/xoroshiro1024.py b/Python3.9/PyRandLib/xoroshiro1024.py new file mode 100644 index 0000000..0503732 --- /dev/null +++ b/Python3.9/PyRandLib/xoroshiro1024.py @@ -0,0 +1,190 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, SeedStateType, StatesListAndExt +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro1024( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro10214** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^1,024 (i.e. about 1.80e+308), jump ahead feature, very short escape from + zeroland (100 iterations) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro1024** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro1024() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE : Final[int] = 16 + _SIZE_MODULO: Final[int] = 0xf # optimization here, to use operand & as modulo computation + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, SeedStateType] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + previousIndex = self._index + # advances the internal state of the PRNG + self._index = (self._index + 1) & Xoroshiro1024._SIZE_MODULO + sHigh = self._state[ previousIndex ] ^ (sLow := self._state[ self._index ]) + self._state[ previousIndex ] = BaseRandom._rotleft( sLow, 25 ) ^ sHigh ^ ((sHigh << 27) & Xoroshiro1024._MODULO) + self._state[ self._index ] = BaseRandom._rotleft( sHigh, 36 ) + # returns the output value + return (BaseRandom._rotleft( sLow * 5, 7) * 9) & Xoroshiro1024._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> list[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return self._state[:] + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: SeedStateType = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + if (count := len( _seedState )) == 0: + self._index = 0 + self._initstate() + + elif count == 1: + self._index = 0 + self._initstate( _seedState[0] ) + + else: + self._initindex( _seedState[1] ) + if (len(_seedState[0]) == Xoroshiro1024._STATE_SIZE): + self._state = _seedState[0][:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._index = 0 + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initindex(self, _index: int, /) -> None: + """Inits the internal index pointing to the internal list. + """ + try: + self._index = int( _index ) & Xoroshiro1024._SIZE_MODULO + except: + self._index = 0 + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro1024._STATE_SIZE) ] + + +#===== end of module xoroshiro1024.py ================================== + diff --git a/Python3.9/PyRandLib/xoroshiro256.py b/Python3.9/PyRandLib/xoroshiro256.py new file mode 100644 index 0000000..84f5f3a --- /dev/null +++ b/Python3.9/PyRandLib/xoroshiro256.py @@ -0,0 +1,181 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro256( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro256** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^256 (i.e. about 1.16e+77), jump ahead feature, very short escape from + zeroland (10 iterations only) and passes TestU01 tests but has shown close repeats + flaws, with a bad Hamming weight near zero (see + https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro256** of the algorithm. + + See Xoroshiro512 for a large 2^512 period (i.e. about 1.34e+154) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Implementation notice: the internal state of this PRNG is coded on four integers + rather than on a list of four integers, to optimize computations time. + + Furthermore this class is callable: + rand = Xoroshiro256() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._s1 + # advances the internal state of the PRNG + self._s2 ^= self._s0 + self._s3 ^= self._s1 + self._s1 ^= self._s2 + self._s0 ^= self._s3 + self._s2 ^= (currentS1 << 17) & BaseXoroshiro._MODULO + self._s3 = BaseRandom._rotleft( self._s3, 45 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + if (count := len( _seedState )) == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == BaseXoroshiro._STATE_SIZE): + self._state = _seedState[:] # each entry in _seedState MUST be integer + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._s0 = initRand() + self._s1 = initRand() + self._s2 = initRand() + self._s3 = initRand() + + +#===== end of module xoroshiro256.py =================================== + diff --git a/Python3.9/PyRandLib/xoroshiro512.py b/Python3.9/PyRandLib/xoroshiro512.py new file mode 100644 index 0000000..0e19e70 --- /dev/null +++ b/Python3.9/PyRandLib/xoroshiro512.py @@ -0,0 +1,181 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from typing import Final, Union + +from .baserandom import BaseRandom +from .basexoroshiro import BaseXoroshiro +from .annotation_types import Numerical, StatesList +from .splitmix import SplitMix64 + + +#============================================================================= +class Xoroshiro512( BaseXoroshiro ): + """The base class for all xoroshiro PRNGs. + + Pseudo-random numbers generator - implements the xoroshiro512** pseudo-random + generator, the four 64-bits integers state array version of the Scrambled Linear + Pseudorandom Number Generators. It provides 64-bits pseudo random values, a medium + period 2^512 (i.e. about 1.34e+154), jump ahead feature, very short escape from + zeroland (30 iterations only) and passes TestU01 tests. + + This module is part of library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The base xoroshiro linear transformation is obtained combining a rotation, a + shift, and again a rotation. An additional scrambling method based on two + multiplications is also computed for this version xoroshiro512** of the algorithm. + + See Xoroshiro256 for a large 2^256 period (i.e. about 1.16e+77) scramble linear + PRNG, with low computation time, 64-bits output values and good randomness + characteristics. + See Xoroshiro1024 for a large 2^1024 period (i.e. about 1.80e+308) scramble linear + PRNG, with low computation time, 64-bits output values and very good randomness + characteristics. + + Furthermore this class is callable: + rand = Xoroshiro512() + print( rand() ) # prints a pseudo-random value within [0.0, 1.0) + print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a + print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a) + + Reminder: + We give you here below a copy of the table of tests for the xoroshiros that have + been implemented in PyRandLib, as described by the authors of xoroshiro - see + reference [10] in file README.md. + + | PyRandLib class | initial xoroshiro algo name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | --------------- | --------------------------- | --------------- | ------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | Xoroshiro256 | xoroshiro256** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xoroshiro512 | xoroshiro512** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xoroshiro1024 | xoroshiro1024** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + + * _small crush_ is a small set of simple tests that quickly tests some of + the expected characteristics for a pretty good PRNG; + * _crush_ is a bigger set of tests that test more deeply expected random + characteristics; + * _big crush_ is the ultimate set of difficult tests that any GOOD PRNG + should definitively pass. + """ + + #------------------------------------------------------------------------- + _STATE_SIZE: Final[int] = 8 + + + #------------------------------------------------------------------------- + def __init__(self, _seedState: Union[Numerical, StatesList] = None, /) -> None: + """Constructor. + + _seedState is either a valid state, an integer, a float or None. + About valid state: this is a tuple containing a list of + self._STATE_SIZE integers and an index in this list (index value + being then in range (0,self._STATE_SIZE)). Should _seedState be + a sole integer or float then it is used as initial seed for + the random filling of the internal list of self._STATE_SIZE + integers. Should _seedState be anything else (e.g. None) then + the shuffling of the local current time value is used as such an + initial seed. + """ + super().__init__( _seedState ) + # this call creates the two attributes + # self._state and self._index, and sets them + # since it internally calls self.setstate(). + + + #------------------------------------------------------------------------- + def next(self) -> int: + """This is the core of the pseudo-random generator. It returns the next pseudo random integer value generated by the inheriting generator. + """ + currentS1 = self._state[1] + # advances the internal state of the PRNG + self._state[2] ^= self._state[0] + self._state[5] ^= self._state[1] + self._state[1] ^= self._state[2] + self._state[7] ^= self._state[3] + self._state[3] ^= self._state[4] + self._state[4] ^= self._state[5] + self._state[0] ^= self._state[6] + self._state[6] ^= self._state[7] + self._state[6] ^= (currentS1 << 11) & BaseXoroshiro._MODULO + self._state[7] = BaseRandom._rotleft( self._state[7], 21 ) + # returns the output value + return (BaseRandom._rotleft( currentS1 * 5, 7) * 9) & BaseXoroshiro._MODULO + + + #------------------------------------------------------------------------- + def getstate(self) -> tuple[ int ]: + """Returns an object capturing the current internal state of the generator. + + This object can be passed to setstate() to restore the state. + It is a tuple containing a list of self._STATE_SIZE integers. + """ + return (self._s0, self._s1, self._s2, self._s3) + + + #------------------------------------------------------------------------- + def setstate(self, _seedState: Union[ Numerical, StatesList ] = None, /) -> None: + """Restores the internal state of the generator. + + _seedState should have been obtained from a previous call to + getstate(), and setstate() restores the internal state of the + generator to what it was at the time setstate() was called. + About valid state: this is a list of self._STATE_SIZE + integers (64-bits). Should _seedState be a sole integer or + float then it is used as initial seed for the random filling + of the internal list of self._STATE_SIZE integers. Should + _seedState be anything else (e.g. None) then the shuffling of + the local current time value is used as such an initial seed. + """ + try: + if (count := len( _seedState )) == 0: + self._initstate() + + elif count == 1: + self._initstate( _seedState[0] ) + + else: + if (len(_seedState[0]) == Xoroshiro512._STATE_SIZE): + self._state = _seedState[:] # Notice: all entries MUST BE integers and not all zero + else: + self._initstate( _seedState[0] ) + + except: + self._initstate( _seedState ) + + + #------------------------------------------------------------------------- + def _initstate(self, _initialSeed: Numerical = None, /) -> None: + """Inits the internal list of values. + + Inits the internal list of values according to some initial + seed that has to be an integer or a float ranging within + [0.0, 1.0). Should it be None or anything else then the + current local time value is used as initial seed value. + """ + initRand = SplitMix64( _initialSeed ) + self._state = [ initRand() for _ in range(Xoroshiro512._STATE_SIZE) ] + + +#===== end of module xoroshiro512.py =================================== + diff --git a/Python3.9/testCPUPerfs.py b/Python3.9/testCPUPerfs.py new file mode 100644 index 0000000..2c7b532 --- /dev/null +++ b/Python3.9/testCPUPerfs.py @@ -0,0 +1,81 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +import sys +from time import perf_counter_ns +from timeit import repeat + +from PyRandLib import * + + +#============================================================================= +def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): + """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" + print("---", prng_class_name, "---") + perfs = repeat("rnd.next()", + setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", + repeat=n_repeats, + timer=perf_counter_ns, + number=n_loops) + print([1e-9 * p / n_loops for p in perfs]) + print(f"--> {min(perfs) / n_loops * 1e-3:.4f} us\n") + + +#============================================================================= +if __name__ == "__main__": + + print("=== PyRandLib CPU time performances ===") + print("Python version:", sys.version, '\n') + + N = 15 + + test_perf("Cwg64" , 0x3ca5_8796 , 100_000, N) + test_perf("Cwg128_64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + + +#===== end of module testCPUPerfs.py =================================== diff --git a/Python3.9/testED.py b/Python3.9/testED.py new file mode 100644 index 0000000..91a4a67 --- /dev/null +++ b/Python3.9/testED.py @@ -0,0 +1,142 @@ +""" +Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +#============================================================================= +from math import sqrt +from statistics import mean, median, stdev +from PyRandLib import * + + +#============================================================================= +def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): + """Tests the equi-distribution of every PRNGs as implemented in library PyRandLib. + + This module is provided with library PyRandLib. + + Copyright (c) 2025 Philippe Schmouker + + The Pseudo-Random Numbers Generators implemented in library PyRandLib have + been chosen as being the best in class ones about their randmoness quality + - as evaluated with test program TestU01 (Pierre L'Ecuyer and Richard + Simard (Université de Montréal) in 'TestU01: A C Library for Empirical + Testing of Random Number Generators - ACM Transactions on Mathematical + Software, vol.33 n.4, pp.22-40, August 2007'). + + One of the main characteristics of these PRNGs is the equidistribution of + the generated random numbers. Validating this equidistribution does not + ensure the correctness of any implementation BUT the failure of this + validation ensures a not correct implementation. This is the sole goal of + this litle script. + + This script runs an N-times loop on each algprithm. At each loop, it draws + a pseudo-random number in the interval [0; 1,000) and sets an histogram of + the drawings (1,000 entries). It then evaluates statistics values mean, + median and standard eviation for each histogram and, for each histogram + entry, evaluates its variance. Should mean value be far from N / 1,000 or + any variance get a too large value, the script outputs on console all + faulty values. + """ + algo_name = rnd_algo.__class__.__name__ + print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') + + hist = [0]*nb_entries + + expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean + expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 + + for _ in range(nb_loops): + n = int(rnd_algo() * nb_entries) + hist[n] += 1 + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") + mn, md, st = mean(hist), median(hist), stdev(hist) + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") + + err = False + + if (abs(md - mn) > expected_max_diff_mean_median): + err = True + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") + if (st > expected_max_stdev): + err = True + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 + + for i in range(nb_entries): + variance = (hist[i] - mn) / st + if abs(variance) > expected_max_variance: + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") + err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") + + if (not err): + print(" Test OK.") + print() + + +#============================================================================= +if __name__ == "__main__": + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + + + +#===== end of module testED.py ========================================= diff --git a/README.md b/README.md index 94f7926..1f5b4e9 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. @@ -45,7 +43,7 @@ The Mersenne twister PRNG proposed by Matsumoto and Nishimura - see [5] - is th implements this PRNG. It is also implemented in C++ and Java standard libraries for instance. -It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, this PRNG is a little bit long to compute (up to 3 times than LCGs, 60% more than LFibs and a little bit less than MRGs, see below at section 'Architecture overview'). Moreover, it fails four of the hardest TestU01 tests. You can still use it as your preferred 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. +It offers a very good period (2^19937, i.e. about 4.3e6001). Unfortunately, this PRNG is a little bit long to compute (up to 3 times than LCGs, 60% more than LFibs and a little bit less than MRGs, see below at section 'Architecture overview'). Moreover, it fails four of the hardest TestU01 tests. You can still use it as your preferred PRNG but **PyRandLib** implements many other PRNGs that are either far faster or far better in terms of generated pseudo-randomness than the Mersenne twister PRG. @@ -62,26 +60,42 @@ 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 | - -(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. +We give you here below a copy of the resulting table for the PRNGs that have been implemented in **PyRandLib**, as provided in [1], plus the Mersenne twister one which is not implemented in **PyRandLib**. +We add in this table the evaluations provided by the authors of every new PRNGs that have been described after the publication of [1]. Fields may be missing then for them. A comparison of the computation times for all implemented PRNGs in **PyRandLib** is provided in an another belowing table. + + | PyRabndLib class | TU01 generator name (1) | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails | + | ---------------- | ---------------------------------- | --------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- | + | 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 | + | 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 | + | Melg607 | *Melg607-64* | 21 x 4-bytes | 2^607 | n.a | n.a. | 0 | 0 | 0 | + | Melg19937 | *Melg19937-64* | 625 x 4-bytes | 2^19,937 | n.a | n.a. | 0 | 0 | 0 | + | Melg44497 | *Melg44497-64* | 1,392 x 4-bytes | 2^44,497 | n.a | n.a. | 0 | 0 | 0 | + | Mrg287 | Marsa-LFIB4 | 256 x 4-bytes | 2^287 | 3.40 | 0.8 | 0 | 0 | 0 | + | Mrg1457 | DX-47-3 | 47 x 4-bytes | 2^1,457 | n.a. | 1.4 | 0 | 0 | 0 | + | Mrg49507 | DX-1597-2-7 | 1,597 x 4-bytes | 2^49,507 | n.a. | 1.4 | 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 | + | 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 | + | 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 (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 | + | Xiroshiro256 | *xiroshiro256*** | 16 x 4-bytes | 2^256 | n.a. | 0.84 | 0 | 0 | 0 | + | Xiroshiro512 | *xiroshiro512*** | 32 x 4-bytes | 2^512 | n.a. | 0.99 | 0 | 0 | 0 | + | Xiroshiro1024 | *xiroshiro1024*** | 64 x 4-bytes | 2^1,024 | n.a. | 1.17 | 0 | 0 | 0 | + +(1)*or the generator original name in the related paper* +(2)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with an associated *tempering* algorithm. @@ -98,29 +112,42 @@ 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. | - - (*missing values in empty columns are to come*) - + | Cwg64 | 0.83 | 0.77 | | | | *0* | *0* | *0* | + | Cwg128_64 | 0.85 | 0.80 | | | | *0* | *0* | *0* | + | Cwg128 | 0.94 | 0.94 | | | | *0* | *0* | *0* | + | FastRand32 | 0.27 | 0.27 | | | | *11* | *106* | *too many* | + | FastRand63 | 0.30 | 0.29 | | | | *0* | *5* | *7* | + | LFib78 | 0.52 | 0.50 | | | | *0* | *0* | *0* | + | LFib116 | 0.53 | 0.52 | | | | *0* | *0* | *0* | + | LFib668 | 0.56 | 0.54 | | | | *0* | *0* | *0* | + | LFib1340 | 0.59 | 0.56 | | | | *0* | *0* | *0* | + | Melg607 | 1.39 | 1.35 | | | | *0* | *0* | *0* | + | Melg19937 | 1.41 | 1.37 | | | | *0* | *0* | *0* | + | Melg44497 | 1.42 | 1.35 | | | | *0* | *0* | *0* | + | Mrg287 | 0.89 | 0.88 | | | | *0* | *0* | *0* | + | Mrg1457 | 0.85 | 0.82 | | | | *0* | *0* | *0* | + | Mrg49507 | 0.75 | 0.69 | | | | *0* | *0* | *0* | + | Pcg64_32 | 0.56 | 0.52 | | | | *0* | *0* | *0* | + | Pcg128_64 | 0.80 | 0.74 | | | | *0* | *0* | *0* | + | Pcg1024_32 | 1.12 | 1.06 | | | | *0* | *0* | *0* | + | Squares32 | 1.58 | 1.47 | | | | *0* | *0* | *0* | + | Squares64 | 1.97 | 1.81 | | | | *0* | *0* | *0* | + | Well512a | 2.80 | 2.74 | | | | *n.a.* | *n.a.* | n.a. | + | Well1024a | 2.52 | 2.44 | | | | *0* | *4* | *4* | + | Well19937c (1) | 3.48 | 3.44 | | | | *0* | *2* | *2* | + | Well44497b | 3.96 | 3.91 | | | | *n.a.* | *n.a.* | n.a. | + | Xiroshiro256 | 2.37 | 2.24 | | | | *0* | *0* | *0* | + | Xiroshiro512 | 2.94 | 2.81 | | | | *0* | *0* | *0* | + | Xiroshiro1024 | 2.78 | 2.59 | | | | *0* | *0* | *0* | + +(1)The Well19937b generator provided with library PyRandLib implements the Well19937a algorithm augmented with a *tempering* algorithm. +(*missing values in empty columns are to come*) ## 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 +189,119 @@ 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. Finally, it is splitted in many subdirectories each dedicated to a specific version of Python: Python3.6, Python3.9, Python3.10, etc. In each of these directories, library **yRandLib** code is fully copied and modified to take benefit of the improvements on new Python versions syntax and features. Download the one version of value for your application to get all **PyRandLib** stuff at its best for your needs. + +Major 2.0 novelties are listed below: + +1. The WELL algorithm (Well-Equilibrated Long-period Linear, see [6], 2006) is now implemented in **PyRandLib**. This algorithm has proven to very quickly escape from the zeroland (up to 1,000 times faster than the Mersenne-Twister algorithm, for instance) while providing large to very large periods and rather small computation time. +In **PyRandLib**, the WELL algorithm is provided in next forms: Well512a, Well1024a, Well19937c and Well44497b which all generate output values coded on 32-bits. + +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 randomness is of up quality. It does not offer jump ahead but multi-streams feature is available via the simple modification of well specified one of the four integers. +In **PyRandLib**, the CWG algorithm is provided in next forms: Cwg64, Cwg64-128 and Cwg128 which generate output values coded on resp. 64-, 64- and 128- bits . + +1. The Squares algorithm (see "Squares: A Fast Counter-Based RNG" [9], 2022) is now implemented in **PyRandLib**. This algorithm is fast, uses two 64-bits integers as its internal state (a counter and a key), gets a period of 2^64 and runs through 4 to 5 rounds of squaring, exchanging high and low bits and xoring intermediate values. Multi-streams feature is available via the value of the key. +In **PyRandLib**, the Squares32 and Squares64 versions of the algorithm are implemented, which provide resp. 32- and 64- bits output values. Caution: the 64-bits versions should not pass the birthday test, which is a randmoness issue, while this is not mentionned in the original paper [9]. + +1. The xiroshiro algorithm ("Scrambled Linear Pseudorandom Number Generators", see [10], 2018) is now implemented in **PyRandLib**, in its *mult-mult* form for the output scrambler. This algorithm is fast, uses 64-bits integers as its internal state and outputs 64-bits values. It uses few memory space (4, 8 or 16 64-bits integers for resp. the 256-, 512- and 1024- versions that are implemented in **PyRandLib**. Notice: the 256 version of the algorithm is know to show close repeats flaws, with a bad Hamming weight near zero. *xoroshiro512* seems to best fit this property, according to the tables proposed by the authors in [10]. -1. The 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 MELG algorithm ("Maximally Equidistributed Long-period Linear Generators", see [11], 2018) is now implemented in **PyRandLib**. It can be considered as an extension of the WELL algorithm, with a miximization of the equidistribution of generated values, making computations on 64-bits integers and outputing 64-bits values. +**PyRandLib** implements its versions numbered 627-64, 19937-64 and 44497-64 related to the power of 2 of their periods: Melg627, Melg19937 and Melg44497. -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 SplitMix algorithm is now implemented in **PyRandLib**. It is used to initialize the internal state of all other PRNGs. It SHOULD NOT be used as a PRNG due to its random poorness. -3. Documentation has been enhanced, with typos fixed also, and erroneous docstrings have been fixed also. +1. Method `bytesrand()` has been added to the Python built-in class `random.Random` since Python 3.9. So, it is also available in **PyRandLib** but for **all** its Python versions: in Python 3.6 its implementation has been added into base class `BaseRandom`. -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. 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. + +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(), + +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`. + -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. +### 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). + +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 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) + +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). + +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 @@ -208,8 +309,7 @@ Notice: starting at PyRandLib 1.2.0, a new signature is available with this bas **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 @@ -217,14 +317,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)*: @@ -232,20 +331,99 @@ 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. -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 -### MRGRand287 - 2^287 periodicity + x(i) = ( x(i-r) op (x(i-k) ) mod m -**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. +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 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 '+'. + + + +### 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 '+'. + + + +### 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 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 '+'. + + + +### Melg627 -- 2^627 periodicity + +**Melg627** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a large period (2^627, i.e. 5.31e+182) and low computation time. The internal state of this PRNG is equivalent to 21 integers 32-bits coded. + +The base MELG algorithm offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations + + + +### Melg19937 -- 2^19937 periodicity + +**Melg19937** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a large period (2^19,937, i.e. 4.32e+6001) and low computation time. The internal state of this PRNG is equivalent to 625 integers 32-bits coded. + +The base MELG algorithm offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations + + + +### Melg44497 -- 2^44497 periodicity + +**Melg44497** implements a fast 64-bits Maximally Equidistributed Long-period Linear Generator (MELG) with a very large period (2^44,497, i.e. 15.1e+13,466) and low computation time. The internal state of this PRNG is equivalent to 1.393 integers 32-bits coded. + +The base MELG algorithm offers large to very large periods with the best known results in the evaluation of their randomness. It escapes the zeroland at a fast pace. Its specializations are set with parameters that ensures the maximized equidistribution. It might be valuable to use these rather than the WELL algorithm derivations + + + +### Mrg287 - 2^287 periodicity + +**Mrg287** 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: @@ -265,20 +443,17 @@ where op is an operation that can be * (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 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: +The Marsa-LIBF4 version, i.e. **Mrg287** 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 +### Mrg1457 - 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). +**Mrg1457** implements a fast 31-bits Multiple Recursive Generator with a longer period than MRGRan287 (2^1457 vs. 2^287, i.e. 4.0e+438 vs. 2.5e+86) and 80 % more computation time but with much less memory space consumption (47 vs. 256 integers 32-bits coded). The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random generator proposed by Deng and Lin, see [2]. The DX-47-3 version uses the recurrence: @@ -286,9 +461,9 @@ The implementation of this MRG 31-bits model is based on DX-47-3 pseudo-random -### MRGRand49507 - 2^49,507 periodicity +### Mrg49507 - 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). +**Mrg49507** 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 Mrg287) 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: @@ -296,69 +471,45 @@ The implementation of this MRG 31-bits model is based on the 'DX-1597-2-7' MRG p -### LFibRand78 - 2^78 periodicity +### Pcg64_32 - 2^64 periodicity -**LFibRand78** implements a fast 64-bits Lagged Fibonacci generator (LFib). -Lagged Fibonacci generators *LFib( m, r, k, op)* use the recurrence +**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). - x(i) = ( x(i-r) op (x(i-k) ) mod m +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. -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: +### Pcg128_64 - 2^128 periodicity - x(i) = ( x(i-5) + x(i-17) ) mod 2^64 +**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). -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). +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 this the *PCG XSL RR 128/64 (LCG)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. -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 -### LFibRand116 - 2^116 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). -**LFibRand116** implements an LFib 64-bits generator proposed by George Marsaglia in [4]. This PRNG uses the recurrence +The underlying algorithm acts as an LCG associated with a final permutation on bits as its final step before outputing next random value, and an array of 32-bits independant MCG (multiplied congruential generators) used to create huge chaos. It is known to succesfully pass all TestU01 tests. It provides multi streams and jump ahead features and is very hard to be reverted and predicted. +**PyRandLib** implements for this the *PCG XSH RS 64/32 (EXT 1024)* version of the PCG algorithm, as explained in [7] and coded in c++ on www.pcg-random.org. - 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 '+'. +### Squares32 - 2^64 periodicity +**Squares32** implements a fast counter-based pseudo-random numbers generator which outputs 32-bits random values. The core of the algorithm evaluates and squares 64-bits intermadiate values then exchanges their higher and lower bits on a four rounds operations. It uses a 64-bits counter and a 64-bits key. It provides multi-streams feature via different values of key and gets robust randomness characteristics. The counter starts counting at 0. Once returning to 0 modulo 2^64 the whole period of the algorithm will have been exhausted. Values for keys have to be cautiously chosen: the **PyRandLib** implementation of the manner to do it as recommended in [9] is of our own but stricly respects the original recommendation. +**PyRandLib** Squares32 class implements the *squares32* version of the algorithm as described in [9]. -### 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). +### Squares64 - 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 '+'. +**Squares64** implements a fast counter-based pseudo-random numbers generator which outputs 64-bits random values. The core of the algorithm evaluates and squares 64-bits intermadiate values then exchanges their higher and lower bits on a five rounds operations. It uses a 64-bits counter and a 64-bits key. It provides multi-streams feature via different values of key and gets robust randomness characteristics. The counter starts counting at 0. Once returning to 0 modulo 2^64 the whole period of the algorithm will have been exhausted. Values for keys have to be cautiously chosen: the **PyRandLib** implementation of the manner to do it as recommended in [9] is of our own but stricly respects the original recommendation. +Notice: this version of the algorithm should not pass the birthday test, which is a randmoness issue, while this is not mentionned in the original paper [9]. +**PyRandLib** Squares64 class implements the *squares64* version of the algorithm as described in [9]. @@ -366,7 +517,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 +527,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 +537,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,17 +547,43 @@ 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. +### Xoroshiro256 - 2^256 periodicity + +**Xoroshiro256** implements version *xoroshiro256*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 4 integers state array. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. + +It offers a medium period of value 2^256 - i.e. 1.16e+77 - with short computation time and 4 integers 64-bits coded memory consumption. +It escapes the zeroland at a fast pace (about 10 loops) and offers jump-ahead feature. Notice: the 256 version of the algorithm has shown close repeats flaws, with a bad Hamming weight near zero - see [https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html](https://www.pcg-random.org/posts/xoshiro-repeat-flaws.html). + + + +### Xoroshiro512 - 2^512 periodicity + +**Xoroshiro512** implements version *xoroshiro512*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 8 integers state array. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. + +It offers a medium period of value 2^512 - i.e. 1.34e+154 - with short computation time and 4 integers 64-bits coded memory consumption. +It escapes the zeroland at a fast pace (about 30 loops) and offers jump-ahead feature. + + + +### Xoroshiro1024 - 2^1024 periodicity + +**Xoroshiro512** implements version *xoroshiro1024*** of the Scrambled Linear Pseudorandom Number Generators algorithm proposed by David Blackman and Sebastiano Vigna in[10]. This xoroshiro linear transformation updates cyclically two words of a 16 integers state array and a 4 bits index. The base xoroshiro linear transformation is obtained combining a rotation, a shift, and again a rotation. It also applies a double multiplication as the scrambler model before outputing values. Internal state and output values are coded on 64 bits. + +It offers a medium period of value 2^1024 - i.e. 1.80e+308 - with short computation time and 4 integers 64-bits coded memory consumption. +It escapes the zeroland at a fast pace (about 100 loops) and offers jump-ahead feature. + + + ## Inherited Distribution and Generic Functions (some of next explanation may be free to exact copy of Python 3.6 documentation. See [https://docs.python.org/3.6/library/random.html?highlight=random#module-random](https://docs.python.org/3.6/library/random.html?highlight=random#module-random)) -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 +618,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 +653,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 +678,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 +691,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 +700,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 +782,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,29 +792,75 @@ number = {2}, pages = {145-150}, year = {2000}, doi = {10.1080/00031305.2000.10474528}, -URL = {ttp://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, +URL = {http://amstat.tandfonline.com/doi/abs/10.1080/00031305.2000.10474528}, eprint = {http://amstat.tandfonline.com/doi/pdf/10.1080/00031305.2000.10474528} } **[3]** Lih-Yuan Deng. 2005. *Efficient and portable multiple recursive generators of large order*. -ACM Transactions on Modeling and Computer. Simulation 15:1. +In ACM Transactions on Modeling and Computer Simulation, Jan. 2005, Vol. 15 Issue 1, pp. 1-13. +DOI: https://doi.org/10.1145/1044322.1044323 **[4]** Georges Marsaglia. 1985. *A current view of random number generators*. In Computer Science and Statistics, Sixteenth Symposium on the Interface. -Elsevier Science Publishers, North-Holland, Amsterdam, 1985, The Netherlands. pp. 3–10. +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. +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. +**[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). +In ACM Transactions on Mathematical Software, Vol. 32, No. 1, March 2006, pp. 1–16. +see [https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf](https://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf). + + +**[7]** Melissa E. O'Neill. 2014. +*PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation*. +Submitted to ACM Transactions on Mathematical Software (47 pages) +Finally: Harvey Mudd College Computer Science Department Technical Report, HMC-CS-2014-0905, Issued: September 5, 2014 (56 pages). +@techreport{oneill:pcg2014, + title = "PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation", + author = "Melissa E. O'Neill", + institution = "Harvey Mudd College", + address = "Claremont, CA", + number = "HMC-CS-2014-0905", + year = "2014", + month = Sep, + xurl = "https://www.cs.hmc.edu/tr/hmc-cs-2014-0905.pdf", +} +see also [https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf](https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf). + + +**[8]** Tomasz R. Dziala. 2023. +*Collatz-Weyl Generators: High Quality and High Throughput Parameterized Pseudorandom Number Generators*. +Published at arXiv, December 2023 (11 pages), +Last reference: arXiv:2312.17043v4 [cs.CE], 2 Dec 2024, see [https://arxiv.org/abs/2312.17043](https://arxiv.org/abs/2312.17043) +DOI: https://doi.org/10.48550/arXiv.2312.17043 + + +**[9]** Bernard Widynski. March 2022. +*Squares: A Fast Counter-Based RNG*. +Published at arXiv, March 2022 (5 pages) +Last reference: arXiv:2004.06278v7 [cs.DS] 13 Mar 2022, see [https://arxiv.org/pdf/2004.06278](https://arxiv.org/pdf/2004.06278). +DOI: https://doi.org/10.48550/arXiv.2004.06278 + + +**[10]** David Blackman, Sebastiano Vigna. 2018. +*Scrambled Linear Pseudorandom Number Generators*. +Published in arXiv, March 2022 (32 pages) +Last reference: arXiv:1805.01407v3 [cs.DS] 28 Mar 2022, see [https://arxiv.org/pdf/1805.01407](https://arxiv.org/pdf/1805.01407). +DOI: https://doi.org/10.48550/arXiv.1805.01407 + + +**[11]** Shin Harase, Takamitsu Kimoto, 2018. +*Implementing 64-bit Maximally Equidistributed F2-Linear Generators with Mersenne Prime Period*. +In ACM Transactions on Mathematical Software, Volume 44, Issue 3, April 2018, Article No. 30 (11 Pages) +Also published in arXiv, March 2022 (11 pages) +Last reference: arXiv:1505.06582v6 [cs.DS] 20 Nov 2017, see [https://arxiv.org/pdf/1505.06582](https://arxiv.org/pdf/1505.06582). +DOI: https://doi.org/10.1145/3159444, https://doi.org/10.48550/arXiv.1505.06582 diff --git a/testCPUPerfs.py b/testCPUPerfs.py index 1d63eac..2c7b532 100644 --- a/testCPUPerfs.py +++ b/testCPUPerfs.py @@ -32,7 +32,7 @@ def test_perf(prng_class_name: str, seed_value: int, n_loops: int, n_repeats: int): """Evaluates the CPU time spent evaluating a number in [0.0, 1.0).""" print("---", prng_class_name, "---") - perfs = repeat("rnd()", + perfs = repeat("rnd.next()", setup=f"from PyRandLib import {prng_class_name}; rnd = {prng_class_name}({seed_value})", repeat=n_repeats, timer=perf_counter_ns, @@ -49,19 +49,33 @@ 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("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("Cwg128" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("FastRand32" , 0x3ca5_8796 , 100_000, N) + test_perf("FastRand63" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib78" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib116" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib668" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("LFib1340" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg607" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg19937" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Melg44497" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Mrg287" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg1457" , 0x3ca5_8796 , 100_000, N) + test_perf("Mrg49507" , 0x3ca5_8796 , 100_000, N) + test_perf("Pcg64_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg128_64" , 0x3ca5_8796_1f2e_b45a_3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Pcg1024_32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares32" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Squares64" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Well512a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well1024a" , 0x3ca5_8796 , 100_000, N) + test_perf("Well19937c" , 0x3ca5_8796 , 100_000, N) + test_perf("Well44497b" , 0x3ca5_8796 , 100_000, N) + test_perf("Xoroshiro256" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro512" , 0x3ca5_8796_1f2e_b45a, 100_000, N) + test_perf("Xoroshiro1024", 0x3ca5_8796_1f2e_b45a, 100_000, N) + #===== end of module testCPUPerfs.py =================================== diff --git a/testED.py b/testED.py index db3b9b7..91a4a67 100644 --- a/testED.py +++ b/testED.py @@ -57,35 +57,50 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): """ algo_name = rnd_algo.__class__.__name__ print('-'*(len(algo_name)+1), algo_name, '-'*(len(algo_name)+1), sep='\n') - print (nb_loops, "loops,", nb_entries, "entries in histogram,", "expected mean:", nb_loops // nb_entries) hist = [0]*nb_entries expected_max_diff_mean_median = (nb_loops / nb_entries) * 0.002 # i.e. difference should be less than 0.2 % of expected mean expected_max_stdev = 1.04 * sqrt(nb_loops / nb_entries) # i.e. +4 % max over expected stdandard deviation - expected_max_variance = 4.5 # this is the absolute value of the expected max + expected_max_variance = 4.5 # this is the absolute value of the expected max on local variance + + if expected_max_diff_mean_median < 0.5: + expected_max_diff_mean_median= 0.5 for _ in range(nb_loops): n = int(rnd_algo() * nb_entries) hist[n] += 1 - + + # uncomment next line if you want to print the content of the histograms + #print(hist, '\n') + + print (f"{nb_loops:,d} loops, {nb_entries:,d} entries in histogram, expected mean: {round(nb_loops / nb_entries):,d}") mn, md, st = mean(hist), median(hist), stdev(hist) - print(f" mean: {mn}, median: {md}, standard deviation: {st:.3f}") + print(f" mean: {mn:,f}, median: {md:,f}, standard deviation: {st:,.3f}") err = False if (abs(md - mn) > expected_max_diff_mean_median): err = True - print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:.1f}") + print(f" incoherence btw. mean and median values, difference expected to be less than {expected_max_diff_mean_median:,.1f}") if (st > expected_max_stdev): err = True - print(f" standard deviation is out of range, should be less than {expected_max_stdev:.3f}") + print(f" standard deviation is out of range, should be less than {expected_max_stdev:_.3f}") + + min_variance = max_variance = 0.0 for i in range(nb_entries): variance = (hist[i] - mn) / st if abs(variance) > expected_max_variance: - print(f" entry {i}: hist = {hist[i]}, variance = {variance} seems too large") + print(f" entry {i:,d}: hist = {hist[i]:,d}, variance = {variance:,.4f} seems too large") err = True + if variance < min_variance: + min_variance = variance + elif variance > max_variance: + max_variance = variance + + print(f" variances are in range [{min_variance:,.3f} ; {'+' if max_variance > 0.0 else ''}{max_variance:,.3f}]", end='') + print(f", min: {min(hist)}, max: {max(hist)}") if (not err): print(" Test OK.") @@ -94,19 +109,34 @@ def test_algo(rnd_algo, nb_entries: int = 1_000, nb_loops: int = 1_000_000): #============================================================================= if __name__ == "__main__": - test_algo(FastRand32(), nb_loops = 2_000_000) - test_algo(FastRand63(), nb_loops = 2_000_000) - test_algo(LFib78(), nb_loops = 2_000_000) - test_algo(LFib116(), nb_loops = 2_000_000) - test_algo(LFib668(), nb_loops = 2_000_000) - test_algo(LFib1340(), nb_loops = 2_000_000) - test_algo(MRGRand287(), nb_loops = 2_000_000) - test_algo(MRGRand1457(), nb_loops = 2_000_000) - test_algo(MRGRand49507(), nb_loops = 2_000_000) - test_algo(Well512a(), nb_loops = 1_500_000) - test_algo(Well1024a(), nb_loops = 1_500_000) - test_algo(Well19937c(), nb_entries = 2000) - test_algo(Well44497b(), nb_entries = 2000) + test_algo(Cwg64(), 3217, nb_loops = 2_000_000) # notice: 3217 is a prime number + test_algo(Cwg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Cwg128(), 3217, nb_loops = 2_000_000) + test_algo(FastRand32(), 3217, nb_loops = 2_000_000) + test_algo(FastRand63(), 3217, nb_loops = 2_000_000) + test_algo(LFib78(), 3217, nb_loops = 2_000_000) + test_algo(LFib116(), 3217, nb_loops = 2_000_000) + test_algo(LFib668(), 3217, nb_loops = 2_000_000) + test_algo(LFib1340(), 3217, nb_loops = 2_000_000) + test_algo(Melg607(), 3217, nb_loops = 2_000_000) + test_algo(Melg19937(), 3217, nb_loops = 2_000_000) + test_algo(Melg44497(), 3217, nb_loops = 2_000_000) + test_algo(Mrg287(), 3217, nb_loops = 2_000_000) + test_algo(Mrg1457(), 3217, nb_loops = 2_000_000) + test_algo(Mrg49507(), 3217, nb_loops = 2_000_000) + test_algo(Pcg64_32(), 3217, nb_loops = 2_000_000) + test_algo(Pcg128_64(), 3217, nb_loops = 2_000_000) + test_algo(Pcg1024_32(), 3217, nb_loops = 2_000_000) + test_algo(Squares32(), 3217, nb_loops = 2_000_000) + test_algo(Squares64(), 3217, nb_loops = 2_000_000) + test_algo(Well512a(), 3217, nb_loops = 1_500_000) + test_algo(Well1024a(), 3217, nb_loops = 1_500_000) + test_algo(Well19937c(), 2029) # notice: 2029 is a prime number + test_algo(Well44497b(), 2029) + test_algo(Xoroshiro256(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro512(), 3217, nb_loops = 2_000_000) + test_algo(Xoroshiro1024(), 3217, nb_loops = 2_000_000) + #===== end of module testED.py =========================================