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
4 changes: 4 additions & 0 deletions PyRandLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
Copyright (c) 2016-2025 Philippe Schmouker, schmouk (at) gmail.com
"""

from .basecwg import BaseCWG
from .baselcg import BaseLCG
from .baselfib64 import BaseLFib64
from .basemrg import BaseMRG
from .baserandom import BaseRandom
from .basewell import BaseWELL
from .cwg64 import Cwg64
from .cwg128_64 import Cwg128_64
from .cwg128 import Cwg128
from .fastrand32 import FastRand32
from .fastrand63 import FastRand63
from .lfib78 import LFib78
Expand Down
9 changes: 5 additions & 4 deletions PyRandLib/annotation_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@
#=============================================================================
from typing import List, Tuple, Union

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


#===== end of PyRandLib.annotation_types ===============================
Expand Down
137 changes: 137 additions & 0 deletions PyRandLib/basecwg.py
Original file line number Diff line number Diff line change
@@ -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 .baserandom import BaseRandom
from .annotation_types import SeedStateType, StatesListAndState


#=============================================================================
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 PRG;
* _crush_ is a bigger set of tests that test more deeply expected random
characteristics;
* _big crush_ is the ultimate set of difficult tests that any GOOD PRG
should definitively pass.
"""

#-------------------------------------------------------------------------
def __init__(self, _seedState: SeedStateType = None) -> None:
"""Constructor.

Should _seedState be None then the local time is used as a seed (with
its shuffled value).
Notice: method setstate() is not implemented in base class BaseRandom.
So, it must be implemented in classes inheriting BaseLCG and it must
initialize attribute self._state.
"""
super().__init__( _seedState ) # this internally calls 'setstate()' which
# MUST be implemented in inheriting classes


#-------------------------------------------------------------------------
def getstate(self) -> StatesListAndState:
"""Returns an object capturing the current internal state of the generator.

This object can be passed to setstate() to restore the state.
For CWG, this state is defined by a list of control values
(a, weyl and s - or a list of 4 coeffs) and an internal state
value, which are used in methods 'next() and 'setstate() of
every inheriting class.

All inheriting classes MUST IMPLEMENT this method.
"""
raise NotImplementedError()


#=============================================================================
class SplitMix:
"""The splitting and mixing algorithm used to intiialize CWGs states.
"""
#-------------------------------------------------------------------------
def __init__(self, _seed: int) -> None:
"""Constructor.
"""
self.state = _seed & 0xffff_ffff_ffff_ffff

#-------------------------------------------------------------------------
def __call__(self, _mask: int = 0xffff_ffff_ffff_ffff) -> int:
"""The shuffle algorithm.
"""
self.state += 0x9e37_79b9_7f4a_7c15
self.state &= 0xffff_ffff_ffff_ffff

z = self.state & _mask
z = ((z ^ (z >> 30)) * 0xbf58476d1ce4e5b9) & _mask
z = ((z ^ (z >> 27)) * 0x94d049bb133111eb) & _mask

return z ^ (z >> 31)


#===== end of module basecwg.py ========================================
4 changes: 2 additions & 2 deletions PyRandLib/baselcg.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ def getstate(self) -> int:
"""Returns an object capturing the current internal state of the generator.

This object can be passed to setstate() to restore the state.
For LCG, the state is defined with a single integer, 'self._value',
which has to be used in methods 'random() and 'setstate() of every
For LCG, the state is defined with a single integer, 'self._state',
which has to be used in methods 'next() and 'setstate() of every
inheriting class.
"""
return self._state
Expand Down
10 changes: 7 additions & 3 deletions PyRandLib/baserandom.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,12 @@ class BaseRandom( Random ):
def __init__(self, _seed: SeedStateType = None) -> None:
"""Constructor.

Should _seed be None or not an integer then the local
time is used (with its shuffled value) as a seed.
Should _seed be None or not a number then the local time is used
(with its shuffled value) as a seed.

Notice: the Python built-in base class random.Random internally
calls method setstate() which MUST be overridden in classes that
inherit from class BaseRandom.
"""
super().__init__( _seed )

Expand Down Expand Up @@ -326,7 +330,7 @@ def seed(self, _seed: SeedStateType = None) -> None:
def __call__(self, _max : Union[Numerical,
Tuple[Numerical],
List[Numerical]] = 1.0,
times: int = 1 ) -> Numerical:
times: int = 1 ) -> Union[Numerical | List[Numerical]]:
"""This class's instances are callable.

The returned value is uniformly contained within the
Expand Down
104 changes: 104 additions & 0 deletions PyRandLib/basesquares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
Copyright (c) 2025 Philippe Schmouker, schmouk (at) gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

#=============================================================================
from .baserandom import BaseRandom
from .annotation_types import SeedStateType, StatesList


#=============================================================================
class BaseSquares( BaseRandom ):
"""Definition of the base class for the Squares counter-based pseudo-random Generator.

This module is part of library PyRandLib.

Copyright (c) 2025 Philippe Schmouker

Squares models are based on an incremented counter and a key. The
algorithm squares a combination of the counter and the key values,
and exchanges the upper and lower bits of the combination, the
whole repeated a number of times (4 to 5 rounds). Output values
are provided on 32-bits or on 64-bits according to the model. See
[9] in README.md.

See Squares32 for a 2^64 (i.e. about 1.84e+19) period PRNG with
low computation time, medium period, 32-bits output values and
very good randomness characteristics.

See Squares64 for a 2^64 (i.e. about 1.84e+19) period PRNG with
low computation time, medium period, 64-bits output values and
very good randomness characteristics.

Furthermore this class is callable:
rand = BaseSquares()# Caution: this is just used as illustrative. This base class cannot be instantiated
print( rand() ) # prints a pseudo-random value within [0.0, 1.0)
print( rand(a) ) # prints a pseudo-random value within [0, a) or [0.0, a) depending on the type of a
print( rand(a, n) ) # prints a list of n pseudo-random values each within [0, a)

Reminder:
We give you here below a copy of the table of tests for the Squares
that have been implemented in PyRandLib, as presented in paper [9]
- see file README.md.

| PyRandLib class | [9] generator name | Memory Usage | Period | time-32bits | time-64 bits | SmallCrush fails | Crush fails | BigCrush fails |
| --------------- | ------------------ | ------------- | -------- | ----------- | ------------ | ---------------- | ----------- | -------------- |
| Squares32 | squares32 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |
| Squares64 | squares64 | 4 x 4-bytes | 2^64 | n.a. | n.a. | 0 | 0 | 0 |_

* _small crush_ is a small set of simple tests that quickly tests some of
the expected characteristics for a pretty good PRG;
* _crush_ is a bigger set of tests that test more deeply expected random
characteristics;
* _big crush_ is the ultimate set of difficult tests that any GOOD PRG
should definitively pass.
"""

#-------------------------------------------------------------------------
def __init__(self, _seedState: SeedStateType = None) -> None:
"""Constructor.

Should _seedState be None then the local time is used as a seed (with
its shuffled value).
Notice: method setstate() is not implemented in base class BaseRandom.
So, it must be implemented in classes inheriting BaseLCG and it must
initialize attribute self._state.
"""
super().__init__( _seedState ) # this internally calls 'setstate()' which
# MUST be implemented in inheriting classes


#-------------------------------------------------------------------------
def getstate(self) -> StatesList:
"""Returns an object capturing the current internal state of the generator.

This object can be passed to setstate() to restore the state.
For CWG, this state is defined by a list of control values
(a, weyl and s - or a list of 4 coeffs) and an internal state
value, which are used in methods 'next() and 'setstate() of
every inheriting class.

All inheriting classes MUST IMPLEMENT this method.
"""
raise NotImplementedError()


#===== end of module basesquares.py ====================================
Loading