diff options
Diffstat (limited to 'doc/source/reference/random')
24 files changed, 1644 insertions, 0 deletions
diff --git a/doc/source/reference/random/brng/dsfmt.rst b/doc/source/reference/random/brng/dsfmt.rst new file mode 100644 index 000000000..a47586a50 --- /dev/null +++ b/doc/source/reference/random/brng/dsfmt.rst @@ -0,0 +1,43 @@ +Double SIMD Mersenne Twister (dSFMT) +------------------------------------ + +.. module:: numpy.random.dsfmt + +.. currentmodule:: numpy.random.dsfmt + + +.. autoclass:: DSFMT + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~DSFMT.seed + ~DSFMT.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~DSFMT.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~DSFMT.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~DSFMT.cffi + ~DSFMT.ctypes + + diff --git a/doc/source/reference/random/brng/index.rst b/doc/source/reference/random/brng/index.rst new file mode 100644 index 000000000..5241f9856 --- /dev/null +++ b/doc/source/reference/random/brng/index.rst @@ -0,0 +1,42 @@ +Basic Random Number Generators +------------------------------ + +.. currentmodule:: numpy.random + +The random values produced by :class:`~RandomGenerator` +are produced by a basic RNG. These basic RNGs do not directly provide +random numbers and only contains methods used for seeding, getting or +setting the state, jumping or advancing the state, and for accessing +low-level wrappers for consumption by code that can efficiently +access the functions provided, e.g., `numba <https://numba.pydata.org>`_. + +Stable RNGs +=========== +These RNGs will be included in future releases. + + +.. toctree:: + :maxdepth: 1 + + DSFMT <dsfmt> + MT19937 <mt19937> + PCG64 <pcg64> + Philox <philox> + ThreeFry <threefry> + XoroShiro128+ <xoroshiro128> + Xorshift1024*φ <xorshift1024> + Xoshiro256** <xoshiro256starstar> + Xoshiro512** <xoshiro512starstar> + + +Experimental RNGs +================= + +These RNGs are currently included for testing but are may not be +permanent. + +.. toctree:: + :maxdepth: 1 + + PCG32 <pcg32> + ThreeFry32 <threefry32> diff --git a/doc/source/reference/random/brng/mt19937.rst b/doc/source/reference/random/brng/mt19937.rst new file mode 100644 index 000000000..1bd3597c8 --- /dev/null +++ b/doc/source/reference/random/brng/mt19937.rst @@ -0,0 +1,42 @@ +Mersenne Twister (MT19937) +-------------------------- + +.. module:: numpy.random.mt19937 + +.. currentmodule:: numpy.random.mt19937 + +.. autoclass:: MT19937 + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~MT19937.seed + ~MT19937.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~MT19937.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~MT19937.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~MT19937.cffi + ~MT19937.ctypes + + diff --git a/doc/source/reference/random/brng/pcg32.rst b/doc/source/reference/random/brng/pcg32.rst new file mode 100644 index 000000000..f079f5a8c --- /dev/null +++ b/doc/source/reference/random/brng/pcg32.rst @@ -0,0 +1,43 @@ +Parallel Congruent Generator (32-bit, PCG32) +-------------------------------------------- + +.. module:: numpy.random.pcg32 + +.. currentmodule:: numpy.random.pcg32 + +.. autoclass:: PCG32 + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~PCG32.seed + ~PCG32.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~PCG32.advance + ~PCG32.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~PCG32.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~PCG32.cffi + ~PCG32.ctypes + + diff --git a/doc/source/reference/random/brng/pcg64.rst b/doc/source/reference/random/brng/pcg64.rst new file mode 100644 index 000000000..93f026fcb --- /dev/null +++ b/doc/source/reference/random/brng/pcg64.rst @@ -0,0 +1,43 @@ +Parallel Congruent Generator (64-bit, PCG64) +-------------------------------------------- + +.. module:: numpy.random.pcg64 + +.. currentmodule:: numpy.random.pcg64 + +.. autoclass:: PCG64 + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~PCG64.seed + ~PCG64.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~PCG64.advance + ~PCG64.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~PCG64.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~PCG64.cffi + ~PCG64.ctypes + + diff --git a/doc/source/reference/random/brng/philox.rst b/doc/source/reference/random/brng/philox.rst new file mode 100644 index 000000000..c1e047c54 --- /dev/null +++ b/doc/source/reference/random/brng/philox.rst @@ -0,0 +1,43 @@ +Philox Counter-based RNG +------------------------ + +.. module:: numpy.random.philox + +.. currentmodule:: numpy.random.philox + +.. autoclass:: Philox + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~Philox.seed + ~Philox.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~Philox.advance + ~Philox.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~Philox.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~Philox.cffi + ~Philox.ctypes + + diff --git a/doc/source/reference/random/brng/threefry.rst b/doc/source/reference/random/brng/threefry.rst new file mode 100644 index 000000000..eefe16ea0 --- /dev/null +++ b/doc/source/reference/random/brng/threefry.rst @@ -0,0 +1,43 @@ +ThreeFry Counter-based RNG +-------------------------- + +.. module:: numpy.random.threefry + +.. currentmodule:: numpy.random.threefry + +.. autoclass:: ThreeFry + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~ThreeFry.seed + ~ThreeFry.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~ThreeFry.advance + ~ThreeFry.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~ThreeFry.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~ThreeFry.cffi + ~ThreeFry.ctypes + + diff --git a/doc/source/reference/random/brng/threefry32.rst b/doc/source/reference/random/brng/threefry32.rst new file mode 100644 index 000000000..f0d3dc281 --- /dev/null +++ b/doc/source/reference/random/brng/threefry32.rst @@ -0,0 +1,43 @@ +ThreeFry32 Counter-based RNG +---------------------------- + +.. module:: numpy.random.threefry32 + +.. currentmodule:: numpy.random.threefry32 + +.. autoclass:: ThreeFry32 + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~ThreeFry32.seed + ~ThreeFry32.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~ThreeFry32.advance + ~ThreeFry32.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~ThreeFry32.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~ThreeFry32.cffi + ~ThreeFry32.ctypes + + diff --git a/doc/source/reference/random/brng/xoroshiro128.rst b/doc/source/reference/random/brng/xoroshiro128.rst new file mode 100644 index 000000000..590552236 --- /dev/null +++ b/doc/source/reference/random/brng/xoroshiro128.rst @@ -0,0 +1,42 @@ +Xoroshiro128+ +------------- + +.. module:: numpy.random.xoroshiro128 + +.. currentmodule:: numpy.random.xoroshiro128 + +.. autoclass:: Xoroshiro128 + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~Xoroshiro128.seed + ~Xoroshiro128.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~Xoroshiro128.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~Xoroshiro128.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~Xoroshiro128.cffi + ~Xoroshiro128.ctypes + + diff --git a/doc/source/reference/random/brng/xorshift1024.rst b/doc/source/reference/random/brng/xorshift1024.rst new file mode 100644 index 000000000..24ed3df04 --- /dev/null +++ b/doc/source/reference/random/brng/xorshift1024.rst @@ -0,0 +1,42 @@ +Xorshift1024*φ +-------------- + +.. module:: numpy.random.xorshift1024 + +.. currentmodule:: numpy.random.xorshift1024 + +.. autoclass:: Xorshift1024 + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~Xorshift1024.seed + ~Xorshift1024.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~Xorshift1024.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~Xorshift1024.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~Xorshift1024.cffi + ~Xorshift1024.ctypes + + diff --git a/doc/source/reference/random/brng/xoshiro256starstar.rst b/doc/source/reference/random/brng/xoshiro256starstar.rst new file mode 100644 index 000000000..85c445666 --- /dev/null +++ b/doc/source/reference/random/brng/xoshiro256starstar.rst @@ -0,0 +1,42 @@ +Xoshiro256** +------------ + +.. module:: numpy.random.xoshiro256starstar + +.. currentmodule:: numpy.random.xoshiro256starstar + +.. autoclass:: Xoshiro256StarStar + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~Xoshiro256StarStar.seed + ~Xoshiro256StarStar.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~Xoshiro256StarStar.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~Xoshiro256StarStar.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~Xoshiro256StarStar.cffi + ~Xoshiro256StarStar.ctypes + + diff --git a/doc/source/reference/random/brng/xoshiro512starstar.rst b/doc/source/reference/random/brng/xoshiro512starstar.rst new file mode 100644 index 000000000..0c008d56e --- /dev/null +++ b/doc/source/reference/random/brng/xoshiro512starstar.rst @@ -0,0 +1,42 @@ +Xoshiro512** +------------ + +.. module:: numpy.random.xoshiro512starstar + +.. currentmodule:: numpy.random.xoshiro512starstar + +.. autoclass:: Xoshiro512StarStar + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~Xoshiro512StarStar.seed + ~Xoshiro512StarStar.state + +Parallel generation +=================== +.. autosummary:: + :toctree: generated/ + + ~Xoshiro512StarStar.jump + +Random Generator +================ +.. autosummary:: + :toctree: generated/ + + ~Xoshiro512StarStar.generator + +Extending +========= +.. autosummary:: + :toctree: generated/ + + ~Xoshiro512StarStar.cffi + ~Xoshiro512StarStar.ctypes + + diff --git a/doc/source/reference/random/change-log.rst b/doc/source/reference/random/change-log.rst new file mode 100644 index 000000000..af3c266ef --- /dev/null +++ b/doc/source/reference/random/change-log.rst @@ -0,0 +1,39 @@ +Change Log for the original bashtage/randomgen repo +--------------------------------------------------- +v1.16.1 +======= +- Synchronized with upstream changes. +- Fixed a bug in gamma generation if the shape parameters is 0.0. + +v1.16.0 +======= +- Fixed a bug that affected ``randomgen.dsfmt.DSFMT`` when calling + ``~randomgen.dsfmt.DSFMT.jump`` or ``randomgen.dsfmt.DSFMT.seed`` + that failed to reset the buffer. This resulted in upto 381 values from the + previous state being used before the buffer was refilled at the new state. +- Fixed bugs in ``randomgen.xoshiro512starstar.Xoshiro512StarStar`` + and ``randomgen.xorshift1024.Xorshift1024`` where the fallback + entropy initialization used too few bytes. This bug is unlikely to be + encountered since this path is only encountered if the system random + number generator fails. +- Synchronized with upstream changes. + +v1.15.1 +======= +- Added Xoshiro256** and Xoshiro512**, the preferred generators of this class. +- Fixed bug in `jump` method of Random123 generators which did nto specify a default value. +- Added support for generating bounded uniform integers using Lemire's method. +- Synchronized with upstream changes, which requires moving the minimum supported NumPy to 1.13. + +v1.15 +===== +- Synced empty choice changes +- Synced upstream docstring changes +- Synced upstream changes in permutation +- Synced upstream doc fixes +- Added absolute_import to avoid import noise on Python 2.7 +- Add legacy generator which allows NumPy replication +- Improve type handling of integers +- Switch to array-fillers for 0 parameter distribution to improve performance +- Small changes to build on manylinux +- Build wheels using multibuild diff --git a/doc/source/reference/random/entropy.rst b/doc/source/reference/random/entropy.rst new file mode 100644 index 000000000..0664da6f9 --- /dev/null +++ b/doc/source/reference/random/entropy.rst @@ -0,0 +1,6 @@ +System Entropy +============== + +.. module:: numpy.random.entropy + +.. autofunction:: random_entropy diff --git a/doc/source/reference/random/extending.rst b/doc/source/reference/random/extending.rst new file mode 100644 index 000000000..f76e3984f --- /dev/null +++ b/doc/source/reference/random/extending.rst @@ -0,0 +1,167 @@ +.. currentmodule:: numpy.random + +Extending +--------- +The basic RNGs have been designed to be extendable using standard tools for +high-performance Python -- numba and Cython. +The `~RandomGenerator` object can also be used with +user-provided basic RNGs as long as these export a small set of required +functions. + +Numba +===== +Numba can be used with either CTypes or CFFI. The current iteration of the +basic RNGs all export a small set of functions through both interfaces. + +This example shows how numba can be used to produce Box-Muller normals using +a pure Python implementation which is then compiled. The random numbers are +provided by ``ctypes.next_double``. + +.. code-block:: python + + from numpy.random import Xoroshiro128 + import numpy as np + import numba as nb + + x = Xoroshiro128() + f = x.ctypes.next_double + s = x.ctypes.state + state_addr = x.ctypes.state_address + + def normals(n, state): + out = np.empty(n) + for i in range((n+1)//2): + x1 = 2.0*f(state) - 1.0 + x2 = 2.0*f(state) - 1.0 + r2 = x1*x1 + x2*x2 + while r2 >= 1.0 or r2 == 0.0: + x1 = 2.0*f(state) - 1.0 + x2 = 2.0*f(state) - 1.0 + r2 = x1*x1 + x2*x2 + g = np.sqrt(-2.0*np.log(r2)/r2) + out[2*i] = g*x1 + if 2*i+1 < n: + out[2*i+1] = g*x2 + return out + + # Compile using Numba + print(normals(10, s).var()) + # Warm up + normalsj = nb.jit(normals, nopython=True) + # Must use state address not state with numba + normalsj(1, state_addr) + %timeit normalsj(1000000, state_addr) + print('1,000,000 Box-Muller (numba/Xoroshiro128) randoms') + %timeit np.random.standard_normal(1000000) + print('1,000,000 Box-Muller (NumPy) randoms') + + +Both CTypes and CFFI allow the more complicated distributions to be used +directly in Numba after compiling the file distributions.c into a DLL or so. +An example showing the use of a more complicated distribution is in the +examples folder. + +.. _randomgen_cython: + +Cython +====== + +Cython can be used to unpack the ``PyCapsule`` provided by a basic RNG. +This example uses `~xoroshiro128.Xoroshiro128` and +``random_gauss_zig``, the Ziggurat-based generator for normals, to fill an +array. The usual caveats for writing high-performance code using Cython -- +removing bounds checks and wrap around, providing array alignment information +-- still apply. + +.. code-block:: cython + + import numpy as np + cimport numpy as np + cimport cython + from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer + from numpy.random.common cimport * + from numpy.random.distributions cimport random_gauss_zig + from numpy.random.xoroshiro128 import Xoroshiro128 + + + @cython.boundscheck(False) + @cython.wraparound(False) + def normals_zig(Py_ssize_t n): + cdef Py_ssize_t i + cdef brng_t *rng + cdef const char *capsule_name = "BasicRNG" + cdef double[::1] random_values + + x = Xoroshiro128() + capsule = x.capsule + # Optional check that the capsule if from a Basic RNG + if not PyCapsule_IsValid(capsule, capsule_name): + raise ValueError("Invalid pointer to anon_func_state") + # Cast the pointer + rng = <brng_t *> PyCapsule_GetPointer(capsule, capsule_name) + random_values = np.empty(n) + for i in range(n): + # Call the function + random_values[i] = random_gauss_zig(rng) + randoms = np.asarray(random_values) + return randoms + + +The basic RNG can also be directly accessed using the members of the basic +RNG structure. + +.. code-block:: cython + + @cython.boundscheck(False) + @cython.wraparound(False) + def uniforms(Py_ssize_t n): + cdef Py_ssize_t i + cdef brng_t *rng + cdef const char *capsule_name = "BasicRNG" + cdef double[::1] random_values + + x = Xoroshiro128() + capsule = x.capsule + # Optional check that the capsule if from a Basic RNG + if not PyCapsule_IsValid(capsule, capsule_name): + raise ValueError("Invalid pointer to anon_func_state") + # Cast the pointer + rng = <brng_t *> PyCapsule_GetPointer(capsule, capsule_name) + random_values = np.empty(n) + for i in range(n): + # Call the function + random_values[i] = rng.next_double(rng.state) + randoms = np.asarray(random_values) + return randoms + +These functions along with a minimal setup file are included in the +examples folder. + +New Basic RNGs +============== +`~RandomGenerator` can be used with other +user-provided basic RNGs. The simplest way to write a new basic RNG is to +examine the pyx file of one of the existing basic RNGs. The key structure +that must be provided is the ``capsule`` which contains a ``PyCapsule`` to a +struct pointer of type ``brng_t``, + +.. code-block:: c + + typedef struct brng { + void *state; + uint64_t (*next_uint64)(void *st); + uint32_t (*next_uint32)(void *st); + double (*next_double)(void *st); + uint64_t (*next_raw)(void *st); + } brng_t; + +which provides 5 pointers. The first is an opaque pointer to the data structure +used by the basic RNG. The next three are function pointers which return the +next 64- and 32-bit unsigned integers, the next random double and the next +raw value. This final function is used for testing and so can be set to +the next 64-bit unsigned integer function if not needed. Functions inside +``RandomGenerator`` use this structure as in + +.. code-block:: c + + brng_state->next_uint64(brng_state->state) diff --git a/doc/source/reference/random/generator.rst b/doc/source/reference/random/generator.rst new file mode 100644 index 000000000..9d248732f --- /dev/null +++ b/doc/source/reference/random/generator.rst @@ -0,0 +1,85 @@ +.. currentmodule:: numpy.random + +Random Generator +---------------- +The `~RandomGenerator` provides access to +a wide range of distributions, and served as a replacement for +:class:`~numpy.random.RandomState`. The main difference between +the two is that ``RandomGenerator`` relies on an additional basic RNG to +manage state and generate the random bits, which are then transformed into +random values from useful distributions. The default basic RNG used by +``RandomGenerator`` is :class:`~xoroshiro128.Xoroshiro128`. The basic RNG can be +changed by passing an instantized basic RNG to ``RandomGenerator``. + + +.. autoclass:: RandomGenerator + :exclude-members: + +Accessing the RNG +================= +.. autosummary:: + :toctree: generated/ + + ~RandomGenerator.brng + +Simple random data +================== +.. autosummary:: + :toctree: generated/ + + ~RandomGenerator.rand + ~RandomGenerator.randn + ~RandomGenerator.randint + ~RandomGenerator.random_integers + ~RandomGenerator.random_sample + ~RandomGenerator.choice + ~RandomGenerator.bytes + +Permutations +============ +.. autosummary:: + :toctree: generated/ + + ~RandomGenerator.shuffle + ~RandomGenerator.permutation + +Distributions +============= +.. autosummary:: + :toctree: generated/ + + ~RandomGenerator.beta + ~RandomGenerator.binomial + ~RandomGenerator.chisquare + ~RandomGenerator.dirichlet + ~RandomGenerator.exponential + ~RandomGenerator.f + ~RandomGenerator.gamma + ~RandomGenerator.geometric + ~RandomGenerator.gumbel + ~RandomGenerator.hypergeometric + ~RandomGenerator.laplace + ~RandomGenerator.logistic + ~RandomGenerator.lognormal + ~RandomGenerator.logseries + ~RandomGenerator.multinomial + ~RandomGenerator.multivariate_normal + ~RandomGenerator.negative_binomial + ~RandomGenerator.noncentral_chisquare + ~RandomGenerator.noncentral_f + ~RandomGenerator.normal + ~RandomGenerator.pareto + ~RandomGenerator.poisson + ~RandomGenerator.power + ~RandomGenerator.rayleigh + ~RandomGenerator.standard_cauchy + ~RandomGenerator.standard_exponential + ~RandomGenerator.standard_gamma + ~RandomGenerator.standard_normal + ~RandomGenerator.standard_t + ~RandomGenerator.triangular + ~RandomGenerator.uniform + ~RandomGenerator.vonmises + ~RandomGenerator.wald + ~RandomGenerator.weibull + ~RandomGenerator.zipf diff --git a/doc/source/reference/random/index.rst b/doc/source/reference/random/index.rst new file mode 100644 index 000000000..1cbf5c685 --- /dev/null +++ b/doc/source/reference/random/index.rst @@ -0,0 +1,221 @@ +.. currentmodule:: numpy.random + +numpy.random +============ + +A `~RandomGenerator` can +be initialized with a number of different Random Number Generators (RNG)s, and +exposes many different probability distributions. + + +Quick Start +----------- + +By default, `RandomGenerator` uses normals provided by +`xoroshiro128.Xoroshiro128` which will be faster than the legacy methods in +`numpy.random.RandomState` + +.. code-block:: python + + # As replacement for numpy.random.RandomState + from numpy import random + random.standard_normal() + +`RandomGenerator` can be used as a direct replacement for +`~RandomState`, although the random values are generated by +`~xoroshiro128.Xoroshiro128`. The `RandomGenerator` holds an instance of a RNG. +It is accessable as ``gen.brng``. + +.. code-block:: python + + # As replacement for RandomState() + from numpy.random import RandomGenerator + rg = RandomGenerator() + rg.standard_normal() + + +Seeds can be passed to any of the basic RNGs. Here `mt19937.MT19937` is used +and the ``RandomGenerator`` is accessed via the attribute `mt19937.MT19937. +generator`. + +.. code-block:: python + + from numpy.random import MT19937 + rg = MT19937(12345).generator + rg.standard_normal() + + +Introduction +------------ +RandomGen takes a different approach to producing random numbers from the +:class:`numpy.random.RandomState` object. Random number generation is +separated into two components, a basic RNG and a random generator. + +The basic RNG has a limited set of responsibilities. It manages the +underlying RNG state and provides functions to produce random doubles and +random unsigned 32- and 64-bit values. The basic random generator also handles +all seeding since this varies when using alternative basic RNGs. + +The `random generator <~RandomGenerator>` takes the +basic RNG-provided functions and transforms them into more useful +distributions, e.g., simulated normal random values. This structure allows +alternative basic RNGs to be used without code duplication. + +The ``RandomGenerator`` is the user-facing object +that is nearly identical to :class:`~.RandomState`. The canonical +method to initialize a generator passes a basic RNG -- `~mt19937.MT19937`, the +underlying RNG in NumPy -- as the sole argument. Note that the basic RNG must +be instantized. + +.. code-block:: python + + from numpy.random import RandomGenerator, MT19937 + rg = RandomGenerator(MT19937()) + rg.random_sample() + +Seed information is directly passed to the basic RNG. + +.. code-block:: python + + rg = RandomGenerator(MT19937(12345)) + rg.random_sample() + +A shorthand method is also available which uses the `~mt19937.MT19937. +generator` property from a basic RNG to access an embedded random generator. + +.. code-block:: python + + rg = MT19937(12345).generator + rg.random_sample() + +What's New or Different +~~~~~~~~~~~~~~~~~~~~~~~ +.. warning:: + + The Box-Muller method used to produce NumPy's normals is no longer available + in `~RandomGenerator`. It is not possible to reproduce the random values + using ``RandomGenerator`` for the normal distribution or any other + distribution that relies on the normal such as the gamma or student's t. + If you require backward compatibility, a legacy generator, `~legacy. + LegacyGenerator`, has been created which can fully reproduce the sequence + produced by NumPy. + +* The normal, exponential and gamma generators use 256-step Ziggurat + methods which are 2-10 times faster than NumPy's Box-Muller or inverse CDF + implementations. +* Optional ``dtype`` argument that accepts ``np.float32`` or ``np.float64`` + to produce either single or double prevision uniform random variables for + select distributions +* Optional ``out`` argument that allows existing arrays to be filled for + select distributions +* `~entropy.random_entropy` provides access to the system + source of randomness that is used in cryptographic applications (e.g., + ``/dev/urandom`` on Unix). +* All basic random generators functions can produce doubles, uint64s and + uint32s via CTypes (`~xoroshiro128.Xoroshiro128.ctypes`) + and CFFI (:meth:`~xoroshiro128.Xoroshiro128.cffi`). + This allows these basic RNGs to be used in numba. +* The basic random number generators can be used in downstream projects via + :ref:`Cython <randomgen_cython>`. +* Support for Lemire’s method [Lemire]_ of generating uniform integers on an + arbitrary interval by setting ``use_masked=True`` in + `~RandomGenerator.randint`. + + +See :ref:`new-or-different` for a complete list of improvements and +differences. + +Parallel Generation +~~~~~~~~~~~~~~~~~~~ + +The included generators can be used in parallel, distributed applications in +one of two ways: + +* :ref:`independent-streams` +* :ref:`jump-and-advance` + +Supported Generators +-------------------- +The main innovation is the inclusion of a number of alternative pseudo-random number +generators, 'in addition' to the standard PRNG in NumPy. The included PRNGs are: + +* MT19937 - The standard NumPy generator. Produces identical results to NumPy + using the same seed/state. Adds a + `~mt19937.MT19937.jump` function that advances the + generator as-if ``2**128`` draws have been made. See `numpy.random`. +* dSFMT - SSE2 enabled versions of the MT19937 generator. Theoretically + the same, but with a different state and so it is not possible to produce a + sequence identical to MT19937. Supports ``jump`` and so can + be used in parallel applications. See the `dSFMT authors' page`_. +* XoroShiro128+ - Improved version of XorShift128+ with better performance + and statistical quality. Like the XorShift generators, it can be jumped + to produce multiple streams in parallel applications. See + `~xoroshiro128.Xoroshiro128.jump` for details. + More information about this PRNG is available at the + `xorshift, xoroshiro and xoshiro authors' page`_. +* XorShift1024*φ - Fast fast generator based on the XSadd + generator. Supports ``jump`` and so can be used in + parallel applications. See the documentation for + `~xorshift1024.Xorshift1024.jump` for details. More + information about these PRNGs is available at the + `xorshift, xoroshiro and xoshiro authors' page`_. +* Xorshiro256** and Xorshiro512** - The most recently introduced XOR, + shift, and rotate generator. Supports ``jump`` and so can be used in + parallel applications. See the documentation for + `~xoshiro256starstar.Xoshirt256StarStar.jump` for + details. More information about these PRNGs is available at the + `xorshift, xoroshiro and xoshiro authors' page`_. +* PCG-64 - Fast generator that support many parallel streams and + can be advanced by an arbitrary amount. See the documentation for + `~pcg64.PCG64.advance`. PCG-64 has a period of + :math:`2^{128}`. See the `PCG author's page`_ for more details about + this class of PRNG. +* ThreeFry and Philox - counter-based generators capable of being advanced an + arbitrary number of steps or generating independent streams. See the + `Random123`_ page for more details about this class of PRNG. + +.. _`dSFMT authors' page`: http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/ +.. _`xorshift, xoroshiro and xoshiro authors' page`: http://xoroshiro.di.unimi.it/ +.. _`PCG author's page`: http://www.pcg-random.org/ +.. _`Random123`: https://www.deshawresearch.com/resources_random123.html + +Random Generator +---------------- +.. toctree:: + :maxdepth: 1 + + generator + legacy + +Basic Random Number Generators +------------------------------ + +.. toctree:: + :maxdepth: 1 + + Basic Random Number Generators <brng/index> + +New Features +------------ +.. toctree:: + :maxdepth: 2 + + Parallel Applications <parallel> + Multithreaded Generation <multithreading> + new-or-different + Comparing Performance <performance> + extending + Reading System Entropy <entropy> + references + +Changes +~~~~~~~ + +This package was developed independently of NumPy and was integrated in version +1.17.0. The original repo is at https://github.com/bashtage/randomgen. + +.. toctree:: + :maxdepth: 2 + + Change Log <change-log> + diff --git a/doc/source/reference/random/legacy.rst b/doc/source/reference/random/legacy.rst new file mode 100644 index 000000000..b9bfabcc7 --- /dev/null +++ b/doc/source/reference/random/legacy.rst @@ -0,0 +1,110 @@ +Legacy Random Generation +------------------------ +The :class:`~legacy.LegacyGenerator` provides access to +legacy generators. These all depend on Box-Muller normals or +inverse CDF exponentials or gammas. This class should only be used +if it is essential to have randoms that are identical to what +would have been produced by NumPy. + +:class:`~legacy.LegacyGenerator` add additional information +to the state which is required when using Box-Muller normals since these +are produced in pairs. It is important to use +:attr:`~legacy.LegacyGenerator.state` +when accessing the state so that these extra values are saved. + +.. warning:: + + :class:`~randomgen.legacy.LegacyGenerator` only contains functions + that have changed. Since it does not contain other functions, it + is not direclty possible to replace :class:`~numpy.random.RandomState`. + In order to full replace :class:`~numpy.random.RandomState`, it is + necessary to use both :class:`~randomgen.legacy.LegacyGenerator` + and :class:`~randomgen.generator.RandomGenerator` both driven + by the same basic RNG. Methods present in :class:`~randomgen.legacy.LegacyGenerator` + must be called from :class:`~randomgen.legacy.LegacyGenerator`. Other Methods + should be called from :class:`~randomgen.generator.RandomGenerator`. + + +.. code-block:: python + + from numpy.random import MT19937 + from numpy.random.legacy import LegacyGenerator + from numpy.random import RandomState + # Use same seed + rs = RandomState(12345) + mt19937 = MT19937(12345) + rg = RandomGenerator(mt19937) + lg = LegacyGenerator(mt19937) + + # Identical output + rs.standard_normal() + lg.standard_normal() + + rs.random_sample() + rg.random_sample() + + rs.standard_exponential() + lg.standard_exponential() + + +.. currentmodule:: numpy.random.legacy + +.. autoclass:: LegacyGenerator + :exclude-members: + +Seeding and State +================= + +.. autosummary:: + :toctree: generated/ + + ~LegacyGenerator.get_state + ~LegacyGenerator.set_state + +Simple random data +================== +.. autosummary:: + :toctree: generated/ + + ~LegacyGenerator.randn + ~LegacyGenerator.randint + ~LegacyGenerator.random_integers + ~LegacyGenerator.random_sample + ~LegacyGenerator.choice + ~LegacyGenerator.bytes + +Permutations +============ +.. autosummary:: + :toctree: generated/ + + ~LegacyGenerator.shuffle + ~LegacyGenerator.permutation + +Distributions +============= +.. autosummary:: + :toctree: generated/ + + ~LegacyGenerator.beta + ~LegacyGenerator.chisquare + ~LegacyGenerator.dirichlet + ~LegacyGenerator.exponential + ~LegacyGenerator.f + ~LegacyGenerator.gamma + ~LegacyGenerator.lognormal + ~LegacyGenerator.multivariate_normal + ~LegacyGenerator.negative_binomial + ~LegacyGenerator.noncentral_chisquare + ~LegacyGenerator.noncentral_f + ~LegacyGenerator.normal + ~LegacyGenerator.pareto + ~LegacyGenerator.power + ~LegacyGenerator.standard_cauchy + ~LegacyGenerator.standard_exponential + ~LegacyGenerator.standard_gamma + ~LegacyGenerator.standard_normal + ~LegacyGenerator.standard_t + ~LegacyGenerator.wald + ~LegacyGenerator.weibull + ~LegacyGenerator.zipf diff --git a/doc/source/reference/random/multithreading.rst b/doc/source/reference/random/multithreading.rst new file mode 100644 index 000000000..f7762b73a --- /dev/null +++ b/doc/source/reference/random/multithreading.rst @@ -0,0 +1,106 @@ +Multithreaded Generation +======================== + +The four core distributions all allow existing arrays to be filled using the +``out`` keyword argument. Existing arrays need to be contiguous and +well-behaved (writable and aligned). Under normal circumstances, arrays +created using the common constructors such as :meth:`numpy.empty` will satisfy +these requirements. + +This example makes use of Python 3 :mod:`concurrent.futures` to fill an array +using multiple threads. Threads are long-lived so that repeated calls do not +require any additional overheads from thread creation. The underlying PRNG is +xorshift2014 which is fast, has a long period and supports using ``jump`` to +advance the state. The random numbers generated are reproducible in the sense +that the same seed will produce the same outputs. + +.. code-block:: ipython + + from numpy.random import Xorshift1024 + import multiprocessing + import concurrent.futures + import numpy as np + + class MultithreadedRNG(object): + def __init__(self, n, seed=None, threads=None): + rg = Xorshift1024(seed) + if threads is None: + threads = multiprocessing.cpu_count() + self.threads = threads + + self._random_generators = [] + for _ in range(0, threads-1): + _rg = Xorshift1024() + _rg.state = rg.state + self._random_generators.append(_rg.generator) + rg.jump() + self._random_generators.append(rg.generator) + + self.n = n + self.executor = concurrent.futures.ThreadPoolExecutor(threads) + self.values = np.empty(n) + self.step = np.ceil(n / threads).astype(np.int) + + def fill(self): + def _fill(random_state, out, first, last): + random_state.standard_normal(out=out[first:last]) + + futures = {} + for i in range(self.threads): + args = (_fill, + self._random_generators[i], + self.values, + i * self.step, + (i + 1) * self.step) + futures[self.executor.submit(*args)] = i + concurrent.futures.wait(futures) + + def __del__(self): + self.executor.shutdown(False) + + +The multithreaded random number generator can be used to fill an array. +The ``values`` attributes shows the zero-value before the fill and the +random value after. + +.. code-block:: ipython + + In [2]: mrng = MultithreadedRNG(10000000, seed=0) + ...: print(mrng.values[-1]) + 0.0 + + In [3]: mrng.fill() + ...: print(mrng.values[-1]) + 3.296046120254392 + +The time required to produce using multiple threads can be compared to +the time required to generate using a single thread. + +.. code-block:: ipython + + In [4]: print(mrng.threads) + ...: %timeit mrng.fill() + + 4 + 32.8 ms ± 2.71 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) + +The single threaded call directly uses the PRNG. + +.. code-block:: ipython + + In [5]: values = np.empty(10000000) + ...: rg = Xorshift1024().generator + ...: %timeit rg.standard_normal(out=values) + + 99.6 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) + +The gains are substantial and the scaling is reasonable even for large that +are only moderately large. The gains are even larger when compared to a call +that does not use an existing array due to array creation overhead. + +.. code-block:: ipython + + In [6]: rg = Xorshift1024().generator + ...: %timeit rg.standard_normal(10000000) + + 125 ms ± 309 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) diff --git a/doc/source/reference/random/new-or-different.rst b/doc/source/reference/random/new-or-different.rst new file mode 100644 index 000000000..7ddebb5b2 --- /dev/null +++ b/doc/source/reference/random/new-or-different.rst @@ -0,0 +1,101 @@ +.. _new-or-different: + +.. currentmodule:: numpy.random + +What's New or Different +----------------------- + +.. warning:: + + The Box-Muller method used to produce NumPy's normals is no longer available + in `~.RandomGenerator`. It is not possible to + reproduce the exact random values using ``RandomGenerator`` for the normal + distribution or any other distribution that relies on the normal such as the + `numpy.random.gamma` or `numpy.random.standard_t`. If you require backward + compatibility, a legacy generator, `~.legacy. + LegacyGenerator`, has been created which can fully reproduce the exact byte + sequence produced by legacy code. + + +* `~.entropy.random_entropy` provides access to the system + source of randomness that is used in cryptographic applications (e.g., + ``/dev/urandom`` on Unix). +* Simulate from the complex normal distribution + (`~.RandomGenerator.complex_normal`) +* The normal, exponential and gamma generators use 256-step Ziggurat + methods which are 2-10 times faster than NumPy's default implementation in + `~.RandomGenerator.standard_normal`, + `~.RandomGenerator.standard_exponential` or + `~.RandomGenerator.standard_gamma`. +* The Box-Muller used to produce NumPy's normals is no longer available. +* All basic random generators functions to produce doubles, uint64s and + uint32s via CTypes (`~.xoroshiro128.Xoroshiro128. + ctypes`) and CFFI (`~.xoroshiro128.Xoroshiro128.cffi`). + This allows these basic RNGs to be used in numba. +* The basic random number generators can be used in downstream projects via + Cython. + + +.. ipython:: python + + from numpy.random import Xoroshiro128 + import numpy.random + rg = Xoroshiro128().generator + %timeit rg.standard_normal(100000) + %timeit numpy.random.standard_normal(100000) + +.. ipython:: python + + %timeit rg.standard_exponential(100000) + %timeit numpy.random.standard_exponential(100000) + +.. ipython:: python + + %timeit rg.standard_gamma(3.0, 100000) + %timeit numpy.random.standard_gamma(3.0, 100000) + +* Optional ``dtype`` argument that accepts ``np.float32`` or ``np.float64`` + to produce either single or double prevision uniform random variables for + select distributions + + * Uniforms (`~.RandomGenerator.random_sample` and + `~.RandomGenerator.rand`) + * Normals (`~.RandomGenerator.standard_normal` and + `~.RandomGenerator.randn`) + * Standard Gammas (`~.RandomGenerator.standard_gamma`) + * Standard Exponentials (`~.RandomGenerator.standard_exponential`) + +.. ipython:: python + + rg.brng.seed(0) + rg.random_sample(3, dtype='d') + rg.brng.seed(0) + rg.random_sample(3, dtype='f') + +* Optional ``out`` argument that allows existing arrays to be filled for + select distributions + + * Uniforms (`~.RandomGenerator.random_sample`) + * Normals (`~.RandomGenerator.standard_normal`) + * Standard Gammas (`~.RandomGenerator.standard_gamma`) + * Standard Exponentials (`~.RandomGenerator.standard_exponential`) + + This allows multithreading to fill large arrays in chunks using suitable + PRNGs in parallel. + +.. ipython:: python + + existing = np.zeros(4) + rg.random_sample(out=existing[:2]) + print(existing) + +.. * For changes since the previous release, see the :ref:`change-log` + +* Support for Lemire’s method of generating uniform integers on an + arbitrary interval by setting ``use_masked=True`` in + (`~.RandomGenerator.randint`). + +.. ipython:: python + + %timeit rg.randint(0, 1535, use_masked=False) + %timeit numpy.random.randint(0, 1535) diff --git a/doc/source/reference/random/parallel.rst b/doc/source/reference/random/parallel.rst new file mode 100644 index 000000000..41e47039d --- /dev/null +++ b/doc/source/reference/random/parallel.rst @@ -0,0 +1,143 @@ +Parallel Random Number Generation +================================= + +There are three strategies implemented that can be used to produce +repeatable pseudo-random numbers across multiple processes (local +or distributed). + +.. _independent-streams: + +.. currentmodule:: numpy.random + +Independent Streams +------------------- + +:class:`~pcg64.PCG64`, :class:`~threefry.ThreeFry` +and :class:`~philox.Philox` support independent streams. This +example shows how many streams can be created by passing in different index +values in the second input while using the same seed in the first. + +.. code-block:: python + + from numpy.random.entropy import random_entropy + from numpy.random import PCG64 + + entropy = random_entropy(4) + # 128-bit number as a seed + seed = sum([int(entropy[i]) * 2 ** (32 * i) for i in range(4)]) + streams = [PCG64(seed, stream) for stream in range(10)] + + +:class:`~philox.Philox` and :class:`~threefry.ThreeFry` are +counter-based RNGs which use a counter and key. Different keys can be used +to produce independent streams. + +.. code-block:: python + + import numpy as np + from numpy.random import ThreeFry + + key = random_entropy(8) + key = key.view(np.uint64) + key[0] = 0 + step = np.zeros(4, dtype=np.uint64) + step[0] = 1 + streams = [ThreeFry(key=key + stream * step) for stream in range(10)] + +.. _jump-and-advance: + +Jump/Advance the PRNG state +--------------------------- + +Jump +**** + +``jump`` advances the state of the PRNG *as-if* a large number of random +numbers have been drawn. The specific number of draws varies by PRNG, and +ranges from :math:`2^{64}` to :math:`2^{512}`. Additionally, the *as-if* +draws also depend on the size of the default random number produced by the +specific PRNG. The PRNGs that support ``jump``, along with the period of +the PRNG, the size of the jump and the bits in the default unsigned random +are listed below. + ++-----------------+-------------------------+-------------------------+-------------------------+ +| PRNG | Period | Jump Size | Bits | ++=================+=========================+=========================+=========================+ +| DSFMT | :math:`2^{19937}` | :math:`2^{128}` | 53 | ++-----------------+-------------------------+-------------------------+-------------------------+ +| MT19937 | :math:`2^{19937}` | :math:`2^{128}` | 32 | ++-----------------+-------------------------+-------------------------+-------------------------+ +| PCG64 | :math:`2^{128}` | :math:`2^{64}` | 64 | ++-----------------+-------------------------+-------------------------+-------------------------+ +| Philox | :math:`2^{256}` | :math:`2^{128}` | 64 | ++-----------------+-------------------------+-------------------------+-------------------------+ +| ThreeFry | :math:`2^{256}` | :math:`2^{128}` | 64 | ++-----------------+-------------------------+-------------------------+-------------------------+ +| Xoroshiro128 | :math:`2^{128}` | :math:`2^{64}` | 64 | ++-----------------+-------------------------+-------------------------+-------------------------+ +| Xorshift1024 | :math:`2^{1024}` | :math:`2^{512}` | 64 | ++-----------------+-------------------------+-------------------------+-------------------------+ + +``jump`` can be used to produce long blocks which should be long enough to not +overlap. + +.. code-block:: python + + from numpy.random.entropy import random_entropy + from numpy.random import Xorshift1024 + + entropy = random_entropy(2).astype(np.uint64) + # 64-bit number as a seed + seed = entropy[0] * 2**32 + entropy[1] + blocked_rng = [] + for i in range(10): + rng = Xorshift1024(seed) + rng.jump(i) + blocked_rng.append(rng) + + +Advance +******* +``advance`` can be used to jump the state an arbitrary number of steps, and so +is a more general approach than ``jump``. :class:`~pcg64.PCG64`, +:class:`~threefry.ThreeFry` and :class:`~philox.Philox` +support ``advance``, and since these also support independent +streams, it is not usually necessary to use ``advance``. + +Advancing a PRNG updates the underlying PRNG state as-if a given number of +calls to the underlying PRNG have been made. In general there is not a +one-to-one relationship between the number output random values from a +particular distribution and the number of draws from the core PRNG. +This occurs for two reasons: + +* The random values are simulated using a rejection-based method + and so, on average, more than one value from the underlying + PRNG is required to generate an single draw. +* The number of bits required to generate a simulated value + differs from the number of bits generated by the underlying + PRNG. For example, two 16-bit integer values can be simulated + from a single draw of a 32-bit PRNG. + +Advancing the PRNG state resets any pre-computed random numbers. This is +required to ensure exact reproducibility. + +This example uses ``advance`` to advance a :class:`~pcg64.PCG64` +generator 2 ** 127 steps to set a sequence of random number generators. + +.. code-block:: python + + from numpy.random import PCG64 + brng = PCG64() + brng_copy = PCG64() + brng_copy.state = brng.state + + advance = 2**127 + brngs = [brng] + for _ in range(9): + brng_copy.advance(advance) + brng = PCG64() + brng.state = brng_copy.state + brngs.append(brng) + +.. end block + diff --git a/doc/source/reference/random/performance.py b/doc/source/reference/random/performance.py new file mode 100644 index 000000000..12cbbc5d3 --- /dev/null +++ b/doc/source/reference/random/performance.py @@ -0,0 +1,74 @@ +from collections import OrderedDict +from timeit import repeat + +import numpy as np +import pandas as pd + +from randomgen import MT19937, DSFMT, ThreeFry, PCG64, Xoroshiro128, \ + Xorshift1024, Philox, Xoshiro256StarStar, Xoshiro512StarStar + +PRNGS = [DSFMT, MT19937, Philox, PCG64, ThreeFry, Xoroshiro128, Xorshift1024, + Xoshiro256StarStar, Xoshiro512StarStar] + +funcs = {'32-bit Unsigned Ints': 'random_uintegers(size=1000000,bits=32)', + '64-bit Unsigned Ints': 'random_uintegers(size=1000000,bits=32)', + 'Uniforms': 'random_sample(size=1000000)', + 'Complex Normals': 'complex_normal(size=1000000)', + 'Normals': 'standard_normal(size=1000000)', + 'Exponentials': 'standard_exponential(size=1000000)', + 'Gammas': 'standard_gamma(3.0,size=1000000)', + 'Binomials': 'binomial(9, .1, size=1000000)', + 'Laplaces': 'laplace(size=1000000)', + 'Poissons': 'poisson(3.0, size=1000000)', } + +setup = """ +from randomgen import {prng} +rg = {prng}().generator +""" + +test = "rg.{func}" +table = OrderedDict() +for prng in PRNGS: + print(prng) + col = OrderedDict() + for key in funcs: + t = repeat(test.format(func=funcs[key]), + setup.format(prng=prng().__class__.__name__), + number=1, repeat=3) + col[key] = 1000 * min(t) + col = pd.Series(col) + table[prng().__class__.__name__] = col + +npfuncs = OrderedDict() +npfuncs.update(funcs) +npfuncs['32-bit Unsigned Ints'] = 'randint(2**32,dtype="uint32",size=1000000)' +npfuncs['64-bit Unsigned Ints'] = 'tomaxint(size=1000000)' +del npfuncs['Complex Normals'] +setup = """ +from numpy.random import RandomState +rg = RandomState() +""" +col = {} +for key in npfuncs: + t = repeat(test.format(func=npfuncs[key]), + setup.format(prng=prng().__class__.__name__), + number=1, repeat=3) + col[key] = 1000 * min(t) +table['NumPy'] = pd.Series(col) + +table = pd.DataFrame(table) +table = table.reindex(table.mean(1).sort_values().index) +order = np.log(table).mean().sort_values().index +table = table.T +table = table.reindex(order) +table = table.T +print(table.to_csv(float_format='%0.1f')) + +rel = table.loc[:, ['NumPy']].values @ np.ones((1, table.shape[1])) / table +rel.pop(rel.columns[0]) +rel = rel.T +rel['Overall'] = np.exp(np.log(rel).mean(1)) +rel *= 100 +rel = np.round(rel) +rel = rel.T +print(rel.to_csv(float_format='%0d')) diff --git a/doc/source/reference/random/performance.rst b/doc/source/reference/random/performance.rst new file mode 100644 index 000000000..321d49454 --- /dev/null +++ b/doc/source/reference/random/performance.rst @@ -0,0 +1,77 @@ +Performance +----------- + +.. py:module:: numpy.random + +.. currentmodule:: numpy.random + +Recommendation +************** +The recommended generator for single use is +:class:`~xoroshiro128.Xoroshiro128`. The recommended generator +for use in large-scale parallel applications is +:class:`~xorshift1024.Xorshift1024` +where the `jump` method is used to advance the state. For very large scale +applications -- requiring 1,000+ independent streams, +:class:`~pcg64.PCG64` or :class:`~threefry.ThreeFry` are +the best choices. + +Timings +******* + +The timings below are the time in ms to produce 1,000,000 random values from a +specific distribution. :class:`~xoroshiro128.Xoroshiro128` is the +fastest, followed by :class:`~xorshift1024.Xorshift1024` and +:class:`~pcg64.PCG64`. The original :class:`~mt19937.MT19937` +generator is much slower since it requires 2 32-bit values to equal the output +of the faster generators. + +Integer performance has a similar ordering although `dSFMT` is slower since +it generates 53-bit floating point values rather than integer values. On the +other hand, it is very fast for uniforms, although slower than `xoroshiro128+`. + +The pattern is similar for other, more complex generators. The normal +performance of NumPy's MT19937 is much lower than the other since it +uses the Box-Muller transformation rather than the Ziggurat generator. The +performance gap for Exponentials is also large due to the cost of computing +the log function to invert the CDF. + +.. csv-table:: + :header: ,Xoroshiro128,Xorshift1024,PCG64,DSFMT,MT19937,Philox,ThreeFry,NumPy + :widths: 14,14,14,14,14,14,14,14,14 + + 32-bit Unsigned Ints,3.0,3.0,3.0,3.5,3.7,6.8,6.6,3.3 + 64-bit Unsigned Ints,2.6,3.0,3.1,3.4,3.8,6.9,6.6,8.8 + Uniforms,3.2,3.8,4.4,5.0,7.4,8.9,9.9,8.8 + Normals,11.0,13.9,13.7,15.8,16.9,17.8,18.8,63.0 + Exponentials,7.0,8.4,9.0,11.2,12.5,14.1,15.0,102.2 + Binomials,20.9,22.6,22.0,21.2,26.7,27.7,29.2,26.5 + Complex Normals,23.2,28.7,29.1,33.2,35.4,37.6,38.6, + Gammas,35.3,38.6,39.2,41.3,46.7,49.4,51.2,98.8 + Laplaces,97.8,99.9,99.8,96.2,104.1,104.6,104.8,104.1 + Poissons,104.8,113.2,113.3,107.6,129.7,135.6,138.1,131.9 + + +The next table presents the performance relative to `xoroshiro128+` in +percentage. The overall performance was computed using a geometric mean. + +.. csv-table:: + :header: ,Xorshift1024,PCG64,DSFMT,MT19937,Philox,ThreeFry,NumPy + :widths: 14,14,14,14,14,14,14,14 + + 32-bit Unsigned Ints,102,99,118,125,229,221,111 + 64-bit Unsigned Ints,114,116,129,143,262,248,331 + Uniforms,116,137,156,231,275,306,274 + Normals,126,124,143,153,161,170,572 + Exponentials,121,130,161,179,203,215,1467 + Binomials,108,105,101,128,133,140,127 + Complex Normals,124,125,143,153,162,166, + Gammas,109,111,117,132,140,145,280 + Laplaces,102,102,98,106,107,107,106 + Poissons,108,108,103,124,129,132,126 + Overall,113,115,125,144,172,177,251 + + +.. note:: + + All timings were taken using Linux on a i5-3570 processor. diff --git a/doc/source/reference/random/references.rst b/doc/source/reference/random/references.rst new file mode 100644 index 000000000..0dc99868f --- /dev/null +++ b/doc/source/reference/random/references.rst @@ -0,0 +1,5 @@ +References +---------- + +.. [Lemire] Daniel Lemire., "Fast Random Integer Generation in an Interval", + CoRR, Aug. 13, 2018, http://arxiv.org/abs/1805.10941. |
