Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions PyRandLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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
Expand All @@ -23,9 +24,12 @@
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
Expand Down
10 changes: 5 additions & 5 deletions PyRandLib/annotation_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
#=============================================================================
from typing import List, Tuple, Union

Numerical = Union[ int, float ]
StatesList = Union[ Tuple[int], List[int] ]
StatesListAndState = Tuple[ StatesList, int ]
StateType = Union[ StatesList, StatesListAndState ]
SeedStateType = Union[ Numerical, StateType ]
Numerical = Union[ int, float ]
StatesList = Union[ Tuple[int], List[int] ]
StatesListAndExt = Union[ Tuple[ StatesList, int ], Tuple[ StatesList, int, int ] ]
StateType = Union[ StatesList, StatesListAndExt ]
SeedStateType = Union[ Numerical, StateType ]


#===== end of PyRandLib.annotation_types ===============================
Expand Down
4 changes: 2 additions & 2 deletions PyRandLib/basecwg.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

#=============================================================================
from .baserandom import BaseRandom
from .annotation_types import SeedStateType, StatesListAndState
from .annotation_types import SeedStateType, StatesListAndExt


#=============================================================================
Expand Down Expand Up @@ -96,7 +96,7 @@ def __init__(self, _seedState: SeedStateType = None) -> None:


#-------------------------------------------------------------------------
def getstate(self) -> StatesListAndState:
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.
Expand Down
199 changes: 199 additions & 0 deletions PyRandLib/basemelg.py
Original file line number Diff line number Diff line change
@@ -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 =======================================
18 changes: 9 additions & 9 deletions PyRandLib/basemrg.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ 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).

Expand All @@ -66,7 +66,7 @@ class BaseMRG( BaseRandom ):
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
Expand All @@ -75,9 +75,9 @@ class BaseMRG( BaseRandom ):

| PyRandLib 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 |
| 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;
Expand Down
20 changes: 10 additions & 10 deletions PyRandLib/baserandom.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ class BaseRandom( Random ):
precision (64-bits calculations) but memory consumption (resp. 17, 55, 607 and
1279 32-bits integers).

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 32-bits 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 (32-bits 47 integers).
See MRGRand49507 for a far larger period (2^49507, i.e. 1.2e+14903) 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
(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).

Expand Down Expand Up @@ -365,15 +365,15 @@ def __call__(self, _max : Union[Numerical,

#-------------------------------------------------------------------------
@classmethod
def _rotleft(cls, _value: int, _rotCount: int) -> int:
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( 0 <=_rotCount <= 64 )
loMask = (1 << (64 - _rotCount)) - 1
hiMask = ((1 << 64) - 1) ^ loMask
hiBits = (_value & hiMask) >> (64 - _rotCount)
#assert 1 <=_rotCount <= _bitsCount
loMask = (1 << (_bitsCount - _rotCount)) - 1
hiMask = ((1 << _bitsCount) - 1) ^ loMask
hiBits = (_value & hiMask) >> (_bitsCount - _rotCount)
return ((_value & loMask) << _rotCount) | hiBits


Expand Down
6 changes: 3 additions & 3 deletions PyRandLib/basewell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading