summaryrefslogtreecommitdiff
path: root/doc/source/reference/random
diff options
context:
space:
mode:
Diffstat (limited to 'doc/source/reference/random')
-rw-r--r--doc/source/reference/random/bit_generators/bitgenerators.rst11
-rw-r--r--doc/source/reference/random/bit_generators/index.rst112
-rw-r--r--doc/source/reference/random/bit_generators/mt19937.rst34
-rw-r--r--doc/source/reference/random/bit_generators/pcg64.rst33
-rw-r--r--doc/source/reference/random/bit_generators/philox.rst35
-rw-r--r--doc/source/reference/random/bit_generators/sfc64.rst28
-rw-r--r--doc/source/reference/random/entropy.rst6
-rw-r--r--doc/source/reference/random/extending.rst165
-rw-r--r--doc/source/reference/random/generator.rst84
-rw-r--r--doc/source/reference/random/index.rst212
-rw-r--r--doc/source/reference/random/legacy.rst125
-rw-r--r--doc/source/reference/random/multithreading.rst108
-rw-r--r--doc/source/reference/random/new-or-different.rst118
-rw-r--r--doc/source/reference/random/parallel.rst193
-rw-r--r--doc/source/reference/random/performance.py87
-rw-r--r--doc/source/reference/random/performance.rst153
16 files changed, 1504 insertions, 0 deletions
diff --git a/doc/source/reference/random/bit_generators/bitgenerators.rst b/doc/source/reference/random/bit_generators/bitgenerators.rst
new file mode 100644
index 000000000..1474f7dac
--- /dev/null
+++ b/doc/source/reference/random/bit_generators/bitgenerators.rst
@@ -0,0 +1,11 @@
+:orphan:
+
+BitGenerator
+------------
+
+.. currentmodule:: numpy.random.bit_generator
+
+.. autosummary::
+ :toctree: generated/
+
+ BitGenerator
diff --git a/doc/source/reference/random/bit_generators/index.rst b/doc/source/reference/random/bit_generators/index.rst
new file mode 100644
index 000000000..35d9e5d09
--- /dev/null
+++ b/doc/source/reference/random/bit_generators/index.rst
@@ -0,0 +1,112 @@
+.. _bit_generator:
+
+.. currentmodule:: numpy.random
+
+Bit Generators
+--------------
+
+The random values produced by :class:`~Generator`
+orignate in a BitGenerator. The BitGenerators 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>`_.
+
+Supported BitGenerators
+=======================
+
+The included BitGenerators are:
+
+* PCG-64 - The default. A fast generator that supports many parallel streams
+ and can be advanced by an arbitrary amount. See the documentation for
+ :meth:`~.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.
+* MT19937 - The standard Python BitGenerator. Adds a `~mt19937.MT19937.jumped`
+ function that returns a new generator with state as-if :math:`2^{128}` draws have
+ been made.
+* Philox - A counter-based generator capable of being advanced an
+ arbitrary number of steps or generating independent streams. See the
+ `Random123`_ page for more details about this class of bit generators.
+* SFC64 - A fast generator based on random invertible mappings. Usually the
+ fastest generator of the four. See the `SFC author's page`_ for (a little)
+ more detail.
+
+.. _`PCG author's page`: http://www.pcg-random.org/
+.. _`Random123`: https://www.deshawresearch.com/resources_random123.html
+.. _`SFC author's page`: http://pracrand.sourceforge.net/RNG_engines.txt
+
+.. toctree::
+ :maxdepth: 1
+
+ BitGenerator <bitgenerators>
+ MT19937 <mt19937>
+ PCG64 <pcg64>
+ Philox <philox>
+ SFC64 <sfc64>
+
+Seeding and Entropy
+-------------------
+
+A BitGenerator provides a stream of random values. In order to generate
+reproducible streams, BitGenerators support setting their initial state via a
+seed. All of the provided BitGenerators will take an arbitrary-sized
+non-negative integer, or a list of such integers, as a seed. BitGenerators
+need to take those inputs and process them into a high-quality internal state
+for the BitGenerator. All of the BitGenerators in numpy delegate that task to
+`~SeedSequence`, which uses hashing techniques to ensure that even low-quality
+seeds generate high-quality initial states.
+
+.. code-block:: python
+
+ from numpy.random import PCG64
+
+ bg = PCG64(12345678903141592653589793)
+
+.. end_block
+
+`~SeedSequence` is designed to be convenient for implementing best practices.
+We recommend that a stochastic program defaults to using entropy from the OS so
+that each run is different. The program should print out or log that entropy.
+In order to reproduce a past value, the program should allow the user to
+provide that value through some mechanism, a command-line argument is common,
+so that the user can then re-enter that entropy to reproduce the result.
+`~SeedSequence` can take care of everything except for communicating with the
+user, which is up to you.
+
+.. code-block:: python
+
+ from numpy.random import PCG64, SeedSequence
+
+ # Get the user's seed somehow, maybe through `argparse`.
+ # If the user did not provide a seed, it should return `None`.
+ seed = get_user_seed()
+ ss = SeedSequence(seed)
+ print('seed = {}'.format(ss.entropy))
+ bg = PCG64(ss)
+
+.. end_block
+
+We default to using a 128-bit integer using entropy gathered from the OS. This
+is a good amount of entropy to initialize all of the generators that we have in
+numpy. We do not recommend using small seeds below 32 bits for general use.
+Using just a small set of seeds to instantiate larger state spaces means that
+there are some initial states that are impossible to reach. This creates some
+biases if everyone uses such values.
+
+There will not be anything *wrong* with the results, per se; even a seed of
+0 is perfectly fine thanks to the processing that `~SeedSequence` does. If you
+just need *some* fixed value for unit tests or debugging, feel free to use
+whatever seed you like. But if you want to make inferences from the results or
+publish them, drawing from a larger set of seeds is good practice.
+
+If you need to generate a good seed "offline", then ``SeedSequence().entropy``
+or using ``secrets.randbits(128)`` from the standard library are both
+convenient ways.
+
+.. autosummary::
+ :toctree: generated/
+
+ SeedSequence
+ bit_generator.ISeedSequence
+ bit_generator.ISpawnableSeedSequence
+ bit_generator.SeedlessSeedSequence
diff --git a/doc/source/reference/random/bit_generators/mt19937.rst b/doc/source/reference/random/bit_generators/mt19937.rst
new file mode 100644
index 000000000..25ba1d7b5
--- /dev/null
+++ b/doc/source/reference/random/bit_generators/mt19937.rst
@@ -0,0 +1,34 @@
+Mersenne Twister (MT19937)
+--------------------------
+
+.. module:: numpy.random.mt19937
+
+.. currentmodule:: numpy.random.mt19937
+
+.. autoclass:: MT19937
+ :exclude-members:
+
+State
+=====
+
+.. autosummary::
+ :toctree: generated/
+
+ ~MT19937.state
+
+Parallel generation
+===================
+.. autosummary::
+ :toctree: generated/
+
+ ~MT19937.jumped
+
+Extending
+=========
+.. autosummary::
+ :toctree: generated/
+
+ ~MT19937.cffi
+ ~MT19937.ctypes
+
+
diff --git a/doc/source/reference/random/bit_generators/pcg64.rst b/doc/source/reference/random/bit_generators/pcg64.rst
new file mode 100644
index 000000000..7aef1e0dd
--- /dev/null
+++ b/doc/source/reference/random/bit_generators/pcg64.rst
@@ -0,0 +1,33 @@
+Parallel Congruent Generator (64-bit, PCG64)
+--------------------------------------------
+
+.. module:: numpy.random.pcg64
+
+.. currentmodule:: numpy.random.pcg64
+
+.. autoclass:: PCG64
+ :exclude-members:
+
+State
+=====
+
+.. autosummary::
+ :toctree: generated/
+
+ ~PCG64.state
+
+Parallel generation
+===================
+.. autosummary::
+ :toctree: generated/
+
+ ~PCG64.advance
+ ~PCG64.jumped
+
+Extending
+=========
+.. autosummary::
+ :toctree: generated/
+
+ ~PCG64.cffi
+ ~PCG64.ctypes
diff --git a/doc/source/reference/random/bit_generators/philox.rst b/doc/source/reference/random/bit_generators/philox.rst
new file mode 100644
index 000000000..5e581e094
--- /dev/null
+++ b/doc/source/reference/random/bit_generators/philox.rst
@@ -0,0 +1,35 @@
+Philox Counter-based RNG
+------------------------
+
+.. module:: numpy.random.philox
+
+.. currentmodule:: numpy.random.philox
+
+.. autoclass:: Philox
+ :exclude-members:
+
+State
+=====
+
+.. autosummary::
+ :toctree: generated/
+
+ ~Philox.state
+
+Parallel generation
+===================
+.. autosummary::
+ :toctree: generated/
+
+ ~Philox.advance
+ ~Philox.jumped
+
+Extending
+=========
+.. autosummary::
+ :toctree: generated/
+
+ ~Philox.cffi
+ ~Philox.ctypes
+
+
diff --git a/doc/source/reference/random/bit_generators/sfc64.rst b/doc/source/reference/random/bit_generators/sfc64.rst
new file mode 100644
index 000000000..dc03820ae
--- /dev/null
+++ b/doc/source/reference/random/bit_generators/sfc64.rst
@@ -0,0 +1,28 @@
+SFC64 Small Fast Chaotic PRNG
+-----------------------------
+
+.. module:: numpy.random.sfc64
+
+.. currentmodule:: numpy.random.sfc64
+
+.. autoclass:: SFC64
+ :exclude-members:
+
+State
+=====
+
+.. autosummary::
+ :toctree: generated/
+
+ ~SFC64.state
+
+Extending
+=========
+.. autosummary::
+ :toctree: generated/
+
+ ~SFC64.cffi
+ ~SFC64.ctypes
+
+
+
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..22f9cb7e4
--- /dev/null
+++ b/doc/source/reference/random/extending.rst
@@ -0,0 +1,165 @@
+.. currentmodule:: numpy.random
+
+Extending
+---------
+The BitGenerators have been designed to be extendable using standard tools for
+high-performance Python -- numba and Cython. The `~Generator` object can also
+be used with user-provided BitGenerators 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
+BitGenerators 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 PCG64
+ import numpy as np
+ import numba as nb
+
+ x = PCG64()
+ 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/PCG64) 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 BitGenerator.
+This example uses `~pcg64.PCG64` 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 import PCG64
+
+
+ @cython.boundscheck(False)
+ @cython.wraparound(False)
+ def normals_zig(Py_ssize_t n):
+ cdef Py_ssize_t i
+ cdef bitgen_t *rng
+ cdef const char *capsule_name = "BitGenerator"
+ cdef double[::1] random_values
+
+ x = PCG64()
+ capsule = x.capsule
+ if not PyCapsule_IsValid(capsule, capsule_name):
+ raise ValueError("Invalid pointer to anon_func_state")
+ rng = <bitgen_t *> PyCapsule_GetPointer(capsule, capsule_name)
+ random_values = np.empty(n)
+ # Best practice is to release GIL and acquire the lock
+ with x.lock, nogil:
+ for i in range(n):
+ random_values[i] = random_gauss_zig(rng)
+ randoms = np.asarray(random_values)
+ return randoms
+
+The BitGenerator 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 bitgen_t *rng
+ cdef const char *capsule_name = "BitGenerator"
+ cdef double[::1] random_values
+
+ x = PCG64()
+ capsule = x.capsule
+ # Optional check that the capsule if from a BitGenerator
+ if not PyCapsule_IsValid(capsule, capsule_name):
+ raise ValueError("Invalid pointer to anon_func_state")
+ # Cast the pointer
+ rng = <bitgen_t *> PyCapsule_GetPointer(capsule, capsule_name)
+ random_values = np.empty(n)
+ with x.lock, nogil:
+ 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
+==============
+`~Generator` can be used with other user-provided BitGenerators. The simplest
+way to write a new BitGenerator is to examine the pyx file of one of the
+existing BitGenerators. The key structure that must be provided is the
+``capsule`` which contains a ``PyCapsule`` to a struct pointer of type
+``bitgen_t``,
+
+.. code-block:: c
+
+ typedef struct bitgen {
+ 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);
+ } bitgen_t;
+
+which provides 5 pointers. The first is an opaque pointer to the data structure
+used by the BitGenerators. 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
+``Generator`` use this structure as in
+
+.. code-block:: c
+
+ bitgen_state->next_uint64(bitgen_state->state)
diff --git a/doc/source/reference/random/generator.rst b/doc/source/reference/random/generator.rst
new file mode 100644
index 000000000..068143270
--- /dev/null
+++ b/doc/source/reference/random/generator.rst
@@ -0,0 +1,84 @@
+.. currentmodule:: numpy.random
+
+Random Generator
+----------------
+The `~Generator` 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 ``Generator`` relies on an additional BitGenerator to
+manage state and generate the random bits, which are then transformed into
+random values from useful distributions. The default BitGenerator used by
+``Generator`` is `~PCG64`. The BitGenerator
+can be changed by passing an instantized BitGenerator to ``Generator``.
+
+
+.. autofunction:: default_rng
+
+.. autoclass:: Generator
+ :exclude-members:
+
+Accessing the BitGenerator
+==========================
+.. autosummary::
+ :toctree: generated/
+
+ ~numpy.random.Generator.bit_generator
+
+Simple random data
+==================
+.. autosummary::
+ :toctree: generated/
+
+ ~numpy.random.Generator.integers
+ ~numpy.random.Generator.random
+ ~numpy.random.Generator.choice
+ ~numpy.random.Generator.bytes
+
+Permutations
+============
+.. autosummary::
+ :toctree: generated/
+
+ ~numpy.random.Generator.shuffle
+ ~numpy.random.Generator.permutation
+
+Distributions
+=============
+.. autosummary::
+ :toctree: generated/
+
+ ~numpy.random.Generator.beta
+ ~numpy.random.Generator.binomial
+ ~numpy.random.Generator.chisquare
+ ~numpy.random.Generator.dirichlet
+ ~numpy.random.Generator.exponential
+ ~numpy.random.Generator.f
+ ~numpy.random.Generator.gamma
+ ~numpy.random.Generator.geometric
+ ~numpy.random.Generator.gumbel
+ ~numpy.random.Generator.hypergeometric
+ ~numpy.random.Generator.laplace
+ ~numpy.random.Generator.logistic
+ ~numpy.random.Generator.lognormal
+ ~numpy.random.Generator.logseries
+ ~numpy.random.Generator.multinomial
+ ~numpy.random.Generator.multivariate_normal
+ ~numpy.random.Generator.negative_binomial
+ ~numpy.random.Generator.noncentral_chisquare
+ ~numpy.random.Generator.noncentral_f
+ ~numpy.random.Generator.normal
+ ~numpy.random.Generator.pareto
+ ~numpy.random.Generator.poisson
+ ~numpy.random.Generator.power
+ ~numpy.random.Generator.rayleigh
+ ~numpy.random.Generator.standard_cauchy
+ ~numpy.random.Generator.standard_exponential
+ ~numpy.random.Generator.standard_gamma
+ ~numpy.random.Generator.standard_normal
+ ~numpy.random.Generator.standard_t
+ ~numpy.random.Generator.triangular
+ ~numpy.random.Generator.uniform
+ ~numpy.random.Generator.vonmises
+ ~numpy.random.Generator.wald
+ ~numpy.random.Generator.weibull
+ ~numpy.random.Generator.zipf
diff --git a/doc/source/reference/random/index.rst b/doc/source/reference/random/index.rst
new file mode 100644
index 000000000..01f9981a2
--- /dev/null
+++ b/doc/source/reference/random/index.rst
@@ -0,0 +1,212 @@
+.. _numpyrandom:
+
+.. py:module:: numpy.random
+
+.. currentmodule:: numpy.random
+
+Random sampling (:mod:`numpy.random`)
+=====================================
+
+Numpy's random number routines produce pseudo random numbers using
+combinations of a `BitGenerator` to create sequences and a `Generator`
+to use those sequences to sample from different statistical distributions:
+
+* BitGenerators: Objects that generate random numbers. These are typically
+ unsigned integer words filled with sequences of either 32 or 64 random bits.
+* Generators: Objects that transform sequences of random bits from a
+ BitGenerator into sequences of numbers that follow a specific probability
+ distribution (such as uniform, Normal or Binomial) within a specified
+ interval.
+
+Since Numpy version 1.17.0 the Generator can be initialized with a
+number of different BitGenerators. It exposes many different probability
+distributions. See `NEP 19 <https://www.numpy.org/neps/
+nep-0019-rng-policy.html>`_ for context on the updated random Numpy number
+routines. The legacy `.RandomState` random number routines are still
+available, but limited to a single BitGenerator.
+
+For convenience and backward compatibility, a single `~.RandomState`
+instance's methods are imported into the numpy.random namespace, see
+:ref:`legacy` for the complete list.
+
+Quick Start
+-----------
+
+By default, `~Generator` uses bits provided by `~pcg64.PCG64` which
+has better statistical properties than the legacy mt19937 random
+number generator in `~.RandomState`.
+
+.. code-block:: python
+
+ # Uses the old numpy.random.RandomState
+ from numpy import random
+ random.standard_normal()
+
+`~Generator` can be used as a replacement for `~.RandomState`. Both class
+instances now hold a internal `BitGenerator` instance to provide the bit
+stream, it is accessible as ``gen.bit_generator``. Some long-overdue API
+cleanup means that legacy and compatibility methods have been removed from
+`~.Generator`
+
+=================== ============== ============
+`~.RandomState` `~.Generator` Notes
+------------------- -------------- ------------
+``random_sample``, ``random`` Compatible with `random.random`
+``rand``
+------------------- -------------- ------------
+``randint``, ``integers`` Add an ``endpoint`` kwarg
+``random_integers``
+------------------- -------------- ------------
+``tomaxint`` removed Use ``integers(0, np.iinfo(np.int).max,``
+ ``endpoint=False)``
+------------------- -------------- ------------
+``seed`` removed Use `~.SeedSequence.spawn`
+=================== ============== ============
+
+See `new-or-different` for more information
+
+.. code-block:: python
+
+ # As replacement for RandomState(); default_rng() instantiates Generator with
+ # the default PCG64 BitGenerator.
+ from numpy.random import default_rng
+ rg = default_rng()
+ rg.standard_normal()
+ rg.bit_generator
+
+Something like the following code can be used to support both ``RandomState``
+and ``Generator``, with the understanding that the interfaces are slightly
+different
+
+.. code-block:: python
+
+ try:
+ rg_integers = rg.integers
+ except AttributeError:
+ rg_integers = rg.randint
+ a = rg_integers(1000)
+
+Seeds can be passed to any of the BitGenerators. The provided value is mixed
+via `~.SeedSequence` to spread a possible sequence of seeds across a wider
+range of initialization states for the BitGenerator. Here `~.PCG64` is used and
+is wrapped with a `~.Generator`.
+
+.. code-block:: python
+
+ from numpy.random import Generator, PCG64
+ rg = Generator(PCG64(12345))
+ rg.standard_normal()
+
+Introduction
+------------
+The new infrastructure takes a different approach to producing random numbers
+from the `~.RandomState` object. Random number generation is separated into
+two components, a bit generator and a random generator.
+
+The `BitGenerator` has a limited set of responsibilities. It manages state
+and provides functions to produce random doubles and random unsigned 32- and
+64-bit values.
+
+The `random generator <Generator>` takes the
+bit generator-provided stream and transforms them into more useful
+distributions, e.g., simulated normal random values. This structure allows
+alternative bit generators to be used with little code duplication.
+
+The `Generator` is the user-facing object that is nearly identical to
+`.RandomState`. The canonical method to initialize a generator passes a
+`~.PCG64` bit generator as the sole argument.
+
+.. code-block:: python
+
+ from numpy.random import default_rng
+ rg = default_rng(12345)
+ rg.random()
+
+One can also instantiate `Generator` directly with a `BitGenerator` instance.
+To use the older `~mt19937.MT19937` algorithm, one can instantiate it directly
+and pass it to `Generator`.
+
+.. code-block:: python
+
+ from numpy.random import Generator, MT19937
+ rg = Generator(MT19937(12345))
+ rg.random()
+
+What's New or Different
+~~~~~~~~~~~~~~~~~~~~~~~
+.. warning::
+
+ The Box-Muller method used to produce NumPy's normals is no longer available
+ in `Generator`. It is not possible to reproduce the exact random
+ values using Generator for the normal distribution or any other
+ distribution that relies on the normal such as the `.RandomState.gamma` or
+ `.RandomState.standard_t`. If you require bitwise backward compatible
+ streams, use `.RandomState`.
+
+* The Generator's normal, exponential and gamma functions 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 BitGenerators can produce doubles, uint64s and uint32s via CTypes
+ (`~.PCG64.ctypes`) and CFFI (`~.PCG64.cffi`). This allows the bit generators
+ to be used in numba.
+* The bit generators can be used in downstream projects via
+ :ref:`Cython <randomgen_cython>`.
+* `~.Generator.integers` is now the canonical way to generate integer
+ random numbers from a discrete uniform distribution. The ``rand`` and
+ ``randn`` methods are only available through the legacy `~.RandomState`.
+ The ``endpoint`` keyword can be used to specify open or closed intervals.
+ This replaces both ``randint`` and the deprecated ``random_integers``.
+* `~.Generator.random` is now the canonical way to generate floating-point
+ random numbers, which replaces `.RandomState.random_sample`,
+ `.RandomState.sample`, and `.RandomState.ranf`. This is consistent with
+ Python's `random.random`.
+* All BitGenerators in numpy use `~SeedSequence` to convert seeds into
+ initialized states.
+
+See :ref:`new-or-different` for a complete list of improvements and
+differences from the traditional ``Randomstate``.
+
+Parallel Generation
+~~~~~~~~~~~~~~~~~~~
+
+The included generators can be used in parallel, distributed applications in
+one of three ways:
+
+* :ref:`seedsequence-spawn`
+* :ref:`independent-streams`
+* :ref:`parallel-jumped`
+
+Concepts
+--------
+.. toctree::
+ :maxdepth: 1
+
+ generator
+ legacy mtrand <legacy>
+ BitGenerators, SeedSequences <bit_generators/index>
+
+Features
+--------
+.. toctree::
+ :maxdepth: 2
+
+ Parallel Applications <parallel>
+ Multithreaded Generation <multithreading>
+ new-or-different
+ Comparing Performance <performance>
+ extending
+ Reading System Entropy <entropy>
+
+Original Source
+~~~~~~~~~~~~~~~
+
+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.
diff --git a/doc/source/reference/random/legacy.rst b/doc/source/reference/random/legacy.rst
new file mode 100644
index 000000000..04d4d3569
--- /dev/null
+++ b/doc/source/reference/random/legacy.rst
@@ -0,0 +1,125 @@
+.. currentmodule:: numpy.random
+
+.. _legacy:
+
+Legacy Random Generation
+------------------------
+The `~mtrand.RandomState` provides access to
+legacy generators. This generator is considered frozen and will have
+no further improvements. It is guaranteed to produce the same values
+as the final point release of NumPy v1.16. 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 previous versions of NumPy.
+
+`~mtrand.RandomState` adds additional information
+to the state which is required when using Box-Muller normals since these
+are produced in pairs. It is important to use
+`~mtrand.RandomState.get_state`, and not the underlying bit generators
+`state`, when accessing the state so that these extra values are saved.
+
+Although we provide the `~mt19937.MT19937` BitGenerator for use independent of
+`~mtrand.RandomState`, note that its default seeding uses `~SeedSequence`
+rather than the legacy seeding algorithm. `~mtrand.RandomState` will use the
+legacy seeding algorithm. The methods to use the legacy seeding algorithm are
+currently private as the main reason to use them is just to implement
+`~mtrand.RandomState`. However, one can reset the state of `~mt19937.MT19937`
+using the state of the `~mtrand.RandomState`:
+
+.. code-block:: python
+
+ from numpy.random import MT19937
+ from numpy.random import RandomState
+
+ rs = RandomState(12345)
+ mt19937 = MT19937()
+ mt19937.state = rs.get_state()
+ rs2 = RandomState(mt19937)
+
+ # Same output
+ rs.standard_normal()
+ rs2.standard_normal()
+
+ rs.random()
+ rs2.random()
+
+ rs.standard_exponential()
+ rs2.standard_exponential()
+
+
+.. currentmodule:: numpy.random.mtrand
+
+.. autoclass:: RandomState
+ :exclude-members:
+
+Seeding and State
+=================
+
+.. autosummary::
+ :toctree: generated/
+
+ ~RandomState.get_state
+ ~RandomState.set_state
+ ~RandomState.seed
+
+Simple random data
+==================
+.. autosummary::
+ :toctree: generated/
+
+ ~RandomState.rand
+ ~RandomState.randn
+ ~RandomState.randint
+ ~RandomState.random_integers
+ ~RandomState.random_sample
+ ~RandomState.choice
+ ~RandomState.bytes
+
+Permutations
+============
+.. autosummary::
+ :toctree: generated/
+
+ ~RandomState.shuffle
+ ~RandomState.permutation
+
+Distributions
+=============
+.. autosummary::
+ :toctree: generated/
+
+ ~RandomState.beta
+ ~RandomState.binomial
+ ~RandomState.chisquare
+ ~RandomState.dirichlet
+ ~RandomState.exponential
+ ~RandomState.f
+ ~RandomState.gamma
+ ~RandomState.geometric
+ ~RandomState.gumbel
+ ~RandomState.hypergeometric
+ ~RandomState.laplace
+ ~RandomState.logistic
+ ~RandomState.lognormal
+ ~RandomState.logseries
+ ~RandomState.multinomial
+ ~RandomState.multivariate_normal
+ ~RandomState.negative_binomial
+ ~RandomState.noncentral_chisquare
+ ~RandomState.noncentral_f
+ ~RandomState.normal
+ ~RandomState.pareto
+ ~RandomState.poisson
+ ~RandomState.power
+ ~RandomState.rayleigh
+ ~RandomState.standard_cauchy
+ ~RandomState.standard_exponential
+ ~RandomState.standard_gamma
+ ~RandomState.standard_normal
+ ~RandomState.standard_t
+ ~RandomState.triangular
+ ~RandomState.uniform
+ ~RandomState.vonmises
+ ~RandomState.wald
+ ~RandomState.weibull
+ ~RandomState.zipf
diff --git a/doc/source/reference/random/multithreading.rst b/doc/source/reference/random/multithreading.rst
new file mode 100644
index 000000000..6883d3672
--- /dev/null
+++ b/doc/source/reference/random/multithreading.rst
@@ -0,0 +1,108 @@
+Multithreaded Generation
+========================
+
+The four core distributions (:meth:`~.Generator.random`,
+:meth:`~.Generator.standard_normal`, :meth:`~.Generator.standard_exponential`,
+and :meth:`~.Generator.standard_gamma`) 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
+BitGenerator is `PCG64` which is fast, has a long period and supports
+using `PCG64.jumped` to return a new generator while advancing 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 Generator, PCG64
+ import multiprocessing
+ import concurrent.futures
+ import numpy as np
+
+ class MultithreadedRNG(object):
+ def __init__(self, n, seed=None, threads=None):
+ rg = PCG64(seed)
+ if threads is None:
+ threads = multiprocessing.cpu_count()
+ self.threads = threads
+
+ self._random_generators = [rg]
+ last_rg = rg
+ for _ in range(0, threads-1):
+ new_rg = last_rg.jumped()
+ self._random_generators.append(new_rg)
+ last_rg = new_rg
+
+ 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 BitGenerator.
+
+.. code-block:: ipython
+
+ In [5]: values = np.empty(10000000)
+ ...: rg = Generator(PCG64())
+ ...: %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 = Generator(PCG64())
+ ...: %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..5442f46c9
--- /dev/null
+++ b/doc/source/reference/random/new-or-different.rst
@@ -0,0 +1,118 @@
+.. _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 `Generator`. It is not possible to reproduce the exact random
+ values using ``Generator`` for the normal distribution or any other
+ distribution that relies on the normal such as the `gamma` or
+ `standard_t`. If you require bitwise backward compatible
+ streams, use `RandomState`.
+
+Quick comparison of legacy `mtrand <legacy>`_ to the new `Generator`
+
+================== ==================== =============
+Feature Older Equivalent Notes
+------------------ -------------------- -------------
+`~.Generator` `~.RandomState` ``Generator`` requires a stream
+ source, called a `BitGenerator
+ <bit_generators>` A number of these
+ are provided. ``RandomState`` uses
+ the Mersenne Twister `~.MT19937` by
+ default, but can also be instantiated
+ with any BitGenerator.
+------------------ -------------------- -------------
+``random`` ``random_sample``, Access the values in a BitGenerator,
+ ``rand`` convert them to ``float64`` in the
+ interval ``[0.0.,`` `` 1.0)``.
+ In addition to the ``size`` kwarg, now
+ supports ``dtype='d'`` or ``dtype='f'``,
+ and an ``out`` kwarg to fill a user-
+ supplied array.
+
+ Many other distributions are also
+ supported.
+------------------ -------------------- -------------
+``integers`` ``randint``, Use the ``endpoint`` kwarg to adjust
+ ``random_integers`` the inclusion or exclution of the
+ ``high`` interval endpoint
+================== ==================== =============
+
+And in more detail:
+
+* `~.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
+ (`~.Generator.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
+ `~.Generator.standard_normal`, `~.Generator.standard_exponential` or
+ `~.Generator.standard_gamma`.
+* `~.Generator.integers` is now the canonical way to generate integer
+ random numbers from a discrete uniform distribution. The ``rand`` and
+ ``randn`` methods are only available through the legacy `~.RandomState`.
+ This replaces both ``randint`` and the deprecated ``random_integers``.
+* The Box-Muller method used to produce NumPy's normals is no longer available.
+* All bit generators can produce doubles, uint64s and
+ uint32s via CTypes (`~PCG64.ctypes`) and CFFI (`~PCG64.cffi`).
+ This allows these bit generators to be used in numba.
+* The bit generators can be used in downstream projects via
+ Cython.
+
+
+.. ipython:: python
+
+ from numpy.random import Generator, PCG64
+ import numpy.random
+ rg = Generator(PCG64())
+ %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 (`~.Generator.random` and `~.Generator.integers`)
+ * Normals (`~.Generator.standard_normal`)
+ * Standard Gammas (`~.Generator.standard_gamma`)
+ * Standard Exponentials (`~.Generator.standard_exponential`)
+
+.. ipython:: python
+
+ rg = Generator(PCG64(0))
+ rg.random(3, dtype='d')
+ rg.random(3, dtype='f')
+
+* Optional ``out`` argument that allows existing arrays to be filled for
+ select distributions
+
+ * Uniforms (`~.Generator.random`)
+ * Normals (`~.Generator.standard_normal`)
+ * Standard Gammas (`~.Generator.standard_gamma`)
+ * Standard Exponentials (`~.Generator.standard_exponential`)
+
+ This allows multithreading to fill large arrays in chunks using suitable
+ BitGenerators in parallel.
+
+.. ipython:: python
+
+ existing = np.zeros(4)
+ rg.random(out=existing[:2])
+ print(existing)
+
diff --git a/doc/source/reference/random/parallel.rst b/doc/source/reference/random/parallel.rst
new file mode 100644
index 000000000..2f79f22d8
--- /dev/null
+++ b/doc/source/reference/random/parallel.rst
@@ -0,0 +1,193 @@
+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).
+
+.. currentmodule:: numpy.random
+
+.. _seedsequence-spawn:
+
+`~SeedSequence` spawning
+------------------------
+
+`~SeedSequence` `implements an algorithm`_ to process a user-provided seed,
+typically as an integer of some size, and to convert it into an initial state for
+a `~BitGenerator`. It uses hashing techniques to ensure that low-quality seeds
+are turned into high quality initial states (at least, with very high
+probability).
+
+For example, `~mt19937.MT19937` has a state consisting of 624
+`uint32` integers. A naive way to take a 32-bit integer seed would be to just set
+the last element of the state to the 32-bit seed and leave the rest 0s. This is
+a valid state for `~mt19937.MT19937`, but not a good one. The Mersenne Twister
+algorithm `suffers if there are too many 0s`_. Similarly, two adjacent 32-bit
+integer seeds (i.e. ``12345`` and ``12346``) would produce very similar
+streams.
+
+`~SeedSequence` avoids these problems by using successions of integer hashes
+with good `avalanche properties`_ to ensure that flipping any bit in the input
+input has about a 50% chance of flipping any bit in the output. Two input seeds
+that are very close to each other will produce initial states that are very far
+from each other (with very high probability). It is also constructed in such
+a way that you can provide arbitrary-sized integers or lists of integers.
+`~SeedSequence` will take all of the bits that you provide and mix them
+together to produce however many bits the consuming `~BitGenerator` needs to
+initialize itself.
+
+These properties together mean that we can safely mix together the usual
+user-provided seed with simple incrementing counters to get `~BitGenerator`
+states that are (to very high probability) independent of each other. We can
+wrap this together into an API that is easy to use and difficult to misuse.
+
+.. code-block:: python
+
+ from numpy.random import SeedSequence, default_rng
+
+ ss = SeedSequence(12345)
+
+ # Spawn off 10 child SeedSequences to pass to child processes.
+ child_seeds = ss.spawn(10)
+ streams = [default_rng(s) for s in child_seeds]
+
+.. end_block
+
+Child `~SeedSequence` objects can also spawn to make grandchildren, and so on.
+Each `~SeedSequence` has its position in the tree of spawned `~SeedSequence`
+objects mixed in with the user-provided seed to generate independent (with very
+high probability) streams.
+
+.. code-block:: python
+
+ grandchildren = child_seeds[0].spawn(4)
+ grand_streams = [default_rng(s) for s in grandchildren]
+
+.. end_block
+
+This feature lets you make local decisions about when and how to split up
+streams without coordination between processes. You do not have to preallocate
+space to avoid overlapping or request streams from a common global service. This
+general "tree-hashing" scheme is `not unique to numpy`_ but not yet widespread.
+Python has increasingly-flexible mechanisms for parallelization available, and
+this scheme fits in very well with that kind of use.
+
+Using this scheme, an upper bound on the probability of a collision can be
+estimated if one knows the number of streams that you derive. `~SeedSequence`
+hashes its inputs, both the seed and the spawn-tree-path, down to a 128-bit
+pool by default. The probability that there is a collision in
+that pool, pessimistically-estimated ([1]_), will be about :math:`n^2*2^{-128}` where
+`n` is the number of streams spawned. If a program uses an aggressive million
+streams, about :math:`2^{20}`, then the probability that at least one pair of
+them are identical is about :math:`2^{-88}`, which is in solidly-ignorable
+territory ([2]_).
+
+.. [1] The algorithm is carefully designed to eliminate a number of possible
+ ways to collide. For example, if one only does one level of spawning, it
+ is guaranteed that all states will be unique. But it's easier to
+ estimate the naive upper bound on a napkin and take comfort knowing
+ that the probability is actually lower.
+
+.. [2] In this calculation, we can ignore the amount of numbers drawn from each
+ stream. Each of the PRNGs we provide has some extra protection built in
+ that avoids overlaps if the `~SeedSequence` pools differ in the
+ slightest bit. `~pcg64.PCG64` has :math:`2^{127}` separate cycles
+ determined by the seed in addition to the position in the
+ :math:`2^{128}` long period for each cycle, so one has to both get on or
+ near the same cycle *and* seed a nearby position in the cycle.
+ `~philox.Philox` has completely independent cycles determined by the seed.
+ `~sfc64.SFC64` incorporates a 64-bit counter so every unique seed is at
+ least :math:`2^{64}` iterations away from any other seed. And
+ finally, `~mt19937.MT19937` has just an unimaginably huge period. Getting
+ a collision internal to `~SeedSequence` is the way a failure would be
+ observed.
+
+.. _`implements an algorithm`: http://www.pcg-random.org/posts/developing-a-seed_seq-alternative.html
+.. _`suffers if there are too many 0s`: http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/emt19937ar.html
+.. _`avalanche properties`: https://en.wikipedia.org/wiki/Avalanche_effect
+.. _`not unique to numpy`: https://www.iro.umontreal.ca/~lecuyer/myftp/papers/parallel-rng-imacs.pdf
+
+
+.. _independent-streams:
+
+Independent Streams
+-------------------
+
+:class:`~philox.Philox` is a counter-based RNG based which generates values by
+encrypting an incrementing counter using weak cryptographic primitives. The
+seed determines the key that is used for the encryption. Unique keys create
+unique, independent streams. :class:`~philox.Philox` lets you bypass the
+seeding algorithm to directly set the 128-bit key. Similar, but different, keys
+will still create independent streams.
+
+.. code-block:: python
+
+ import secrets
+ from numpy.random import Philox
+
+ # 128-bit number as a seed
+ root_seed = secrets.getrandbits(128)
+ streams = [Philox(key=root_seed + stream_id) for stream_id in range(10)]
+
+.. end_block
+
+This scheme does require that you avoid reusing stream IDs. This may require
+coordination between the parallel processes.
+
+
+.. _parallel-jumped:
+
+Jumping the BitGenerator state
+------------------------------
+
+``jumped`` advances the state of the BitGenerator *as-if* a large number of
+random numbers have been drawn, and returns a new instance with this state.
+The specific number of draws varies by BitGenerator, and ranges from
+:math:`2^{64}` to :math:`2^{128}`. Additionally, the *as-if* draws also depend
+on the size of the default random number produced by the specific BitGenerator.
+The BitGenerators that support ``jumped``, along with the period of the
+BitGenerator, the size of the jump and the bits in the default unsigned random
+are listed below.
+
++-----------------+-------------------------+-------------------------+-------------------------+
+| BitGenerator | Period | Jump Size | Bits |
++=================+=========================+=========================+=========================+
+| MT19937 | :math:`2^{19937}` | :math:`2^{128}` | 32 |
++-----------------+-------------------------+-------------------------+-------------------------+
+| PCG64 | :math:`2^{128}` | :math:`~2^{127}` ([3]_) | 64 |
++-----------------+-------------------------+-------------------------+-------------------------+
+| Philox | :math:`2^{256}` | :math:`2^{128}` | 64 |
++-----------------+-------------------------+-------------------------+-------------------------+
+
+.. [3] The jump size is :math:`(\phi-1)*2^{128}` where :math:`\phi` is the
+ golden ratio. As the jumps wrap around the period, the actual distances
+ between neighboring streams will slowly grow smaller than the jump size,
+ but using the golden ratio this way is a classic method of constructing
+ a low-discrepancy sequence that spreads out the states around the period
+ optimally. You will not be able to jump enough to make those distances
+ small enough to overlap in your lifetime.
+
+``jumped`` can be used to produce long blocks which should be long enough to not
+overlap.
+
+.. code-block:: python
+
+ import secrets
+ from numpy.random import PCG64
+
+ seed = secrets.getrandbits(128)
+ blocked_rng = []
+ rng = PCG64(seed)
+ for i in range(10):
+ blocked_rng.append(rng.jumped(i))
+
+.. end_block
+
+When using ``jumped``, one does have to take care not to jump to a stream that
+was already used. In the above example, one could not later use
+``blocked_rng[0].jumped()`` as it would overlap with ``blocked_rng[1]``. Like
+with the independent streams, if the main process here wants to split off 10
+more streams by jumping, then it needs to start with ``range(10, 20)``,
+otherwise it would recreate the same streams. On the other hand, if you
+carefully construct the streams, then you are guaranteed to have streams that
+do not overlap.
diff --git a/doc/source/reference/random/performance.py b/doc/source/reference/random/performance.py
new file mode 100644
index 000000000..28a42eb0d
--- /dev/null
+++ b/doc/source/reference/random/performance.py
@@ -0,0 +1,87 @@
+from collections import OrderedDict
+from timeit import repeat
+
+import pandas as pd
+
+import numpy as np
+from numpy.random import MT19937, PCG64, Philox, SFC64
+
+PRNGS = [MT19937, PCG64, Philox, SFC64]
+
+funcs = OrderedDict()
+integers = 'integers(0, 2**{bits},size=1000000, dtype="uint{bits}")'
+funcs['32-bit Unsigned Ints'] = integers.format(bits=32)
+funcs['64-bit Unsigned Ints'] = integers.format(bits=64)
+funcs['Uniforms'] = 'random(size=1000000)'
+funcs['Normals'] = 'standard_normal(size=1000000)'
+funcs['Exponentials'] = 'standard_exponential(size=1000000)'
+funcs['Gammas'] = 'standard_gamma(3.0,size=1000000)'
+funcs['Binomials'] = 'binomial(9, .1, size=1000000)'
+funcs['Laplaces'] = 'laplace(size=1000000)'
+funcs['Poissons'] = 'poisson(3.0, size=1000000)'
+
+setup = """
+from numpy.random import {prng}, Generator
+rg = Generator({prng}())
+"""
+
+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'] = 'randint(2**64,dtype="uint64",size=1000000)'
+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['RandomState'] = pd.Series(col)
+
+columns = ['MT19937','PCG64','Philox','SFC64', 'RandomState']
+table = pd.DataFrame(table)
+order = np.log(table).mean().sort_values().index
+table = table.T
+table = table.reindex(columns)
+table = table.T
+table = table.reindex([k for k in funcs], axis=0)
+print(table.to_csv(float_format='%0.1f'))
+
+
+rel = table.loc[:, ['RandomState']].values @ np.ones(
+ (1, table.shape[1])) / table
+rel.pop('RandomState')
+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'))
+
+# Cross-platform table
+rows = ['32-bit Unsigned Ints','64-bit Unsigned Ints','Uniforms','Normals','Exponentials']
+xplat = rel.reindex(rows, axis=0)
+xplat = 100 * (xplat / xplat.MT19937.values[:,None])
+overall = np.exp(np.log(xplat).mean(0))
+xplat = xplat.T.copy()
+xplat['Overall']=overall
+print(xplat.T.round(1))
+
+
+
diff --git a/doc/source/reference/random/performance.rst b/doc/source/reference/random/performance.rst
new file mode 100644
index 000000000..2d5fca496
--- /dev/null
+++ b/doc/source/reference/random/performance.rst
@@ -0,0 +1,153 @@
+Performance
+-----------
+
+.. currentmodule:: numpy.random
+
+Recommendation
+**************
+The recommended generator for general use is :class:`~pcg64.PCG64`. It is
+statistically high quality, full-featured, and fast on most platforms, but
+somewhat slow when compiled for 32-bit processes.
+
+:class:`~philox.Philox` is fairly slow, but its statistical properties have
+very high quality, and it is easy to get assuredly-independent stream by using
+unique keys. If that is the style you wish to use for parallel streams, or you
+are porting from another system that uses that style, then
+:class:`~philox.Philox` is your choice.
+
+:class:`~sfc64.SFC64` is statistically high quality and very fast. However, it
+lacks jumpability. If you are not using that capability and want lots of speed,
+even on 32-bit processes, this is your choice.
+
+:class:`~mt19937.MT19937` `fails some statistical tests`_ and is not especially
+fast compared to modern PRNGs. For these reasons, we mostly do not recommend
+using it on its own, only through the legacy `~.RandomState` for
+reproducing old results. That said, it has a very long history as a default in
+many systems.
+
+.. _`fails some statistical tests`: https://www.iro.umontreal.ca/~lecuyer/myftp/papers/testu01.pdf
+
+Timings
+*******
+
+The timings below are the time in ns to produce 1 random value from a
+specific distribution. 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.
+
+The pattern is similar for other, more complex generators. The normal
+performance of the legacy :class:`~.RandomState` generator 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.
+The column labeled MT19973 is used the same 32-bit generator as
+:class:`~.RandomState` but produces random values using
+:class:`~Generator`.
+
+.. csv-table::
+ :header: ,MT19937,PCG64,Philox,SFC64,RandomState
+ :widths: 14,14,14,14,14,14
+
+ 32-bit Unsigned Ints,3.2,2.7,4.9,2.7,3.2
+ 64-bit Unsigned Ints,5.6,3.7,6.3,2.9,5.7
+ Uniforms,7.3,4.1,8.1,3.1,7.3
+ Normals,13.1,10.2,13.5,7.8,34.6
+ Exponentials,7.9,5.4,8.5,4.1,40.3
+ Gammas,34.8,28.0,34.7,25.1,58.1
+ Binomials,25.0,21.4,26.1,19.5,25.2
+ Laplaces,45.1,40.7,45.5,38.1,45.6
+ Poissons,67.6,52.4,69.2,46.4,78.1
+
+The next table presents the performance in percentage relative to values
+generated by the legacy generator, `RandomState(MT19937())`. The overall
+performance was computed using a geometric mean.
+
+.. csv-table::
+ :header: ,MT19937,PCG64,Philox,SFC64
+ :widths: 14,14,14,14,14
+
+ 32-bit Unsigned Ints,101,121,67,121
+ 64-bit Unsigned Ints,102,156,91,199
+ Uniforms,100,179,90,235
+ Normals,263,338,257,443
+ Exponentials,507,752,474,985
+ Gammas,167,207,167,231
+ Binomials,101,118,96,129
+ Laplaces,101,112,100,120
+ Poissons,116,149,113,168
+ Overall,144,192,132,225
+
+.. note::
+
+ All timings were taken using Linux on a i5-3570 processor.
+
+Performance on different Operating Systems
+******************************************
+Performance differs across platforms due to compiler and hardware availability
+(e.g., register width) differences. The default bit generator has been chosen
+to perform well on 64-bit platforms. Performance on 32-bit operating systems
+is very different.
+
+The values reported are normalized relative to the speed of MT19937 in
+each table. A value of 100 indicates that the performance matches the MT19937.
+Higher values indicate improved performance. These values cannot be compared
+across tables.
+
+64-bit Linux
+~~~~~~~~~~~~
+
+=================== ========= ======= ======== =======
+Distribution MT19937 PCG64 Philox SFC64
+=================== ========= ======= ======== =======
+32-bit Unsigned Int 100 119.8 67.7 120.2
+64-bit Unsigned Int 100 152.9 90.8 213.3
+Uniforms 100 179.0 87.0 232.0
+Normals 100 128.5 99.2 167.8
+Exponentials 100 148.3 93.0 189.3
+**Overall** 100 144.3 86.8 180.0
+=================== ========= ======= ======== =======
+
+
+64-bit Windows
+~~~~~~~~~~~~~~
+The relative performance on 64-bit Linux and 64-bit Windows is broadly similar.
+
+
+=================== ========= ======= ======== =======
+Distribution MT19937 PCG64 Philox SFC64
+=================== ========= ======= ======== =======
+32-bit Unsigned Int 100 129.1 35.0 135.0
+64-bit Unsigned Int 100 146.9 35.7 176.5
+Uniforms 100 165.0 37.0 192.0
+Normals 100 128.5 48.5 158.0
+Exponentials 100 151.6 39.0 172.8
+**Overall** 100 143.6 38.7 165.7
+=================== ========= ======= ======== =======
+
+
+32-bit Windows
+~~~~~~~~~~~~~~
+
+The performance of 64-bit generators on 32-bit Windows is much lower than on 64-bit
+operating systems due to register width. MT19937, the generator that has been
+in NumPy since 2005, operates on 32-bit integers.
+
+=================== ========= ======= ======== =======
+Distribution MT19937 PCG64 Philox SFC64
+=================== ========= ======= ======== =======
+32-bit Unsigned Int 100 30.5 21.1 77.9
+64-bit Unsigned Int 100 26.3 19.2 97.0
+Uniforms 100 28.0 23.0 106.0
+Normals 100 40.1 31.3 112.6
+Exponentials 100 33.7 26.3 109.8
+**Overall** 100 31.4 23.8 99.8
+=================== ========= ======= ======== =======
+
+
+.. note::
+
+ Linux timings used Ubuntu 18.04 and GCC 7.4. Windows timings were made on
+ Windows 10 using Microsoft C/C++ Optimizing Compiler Version 19 (Visual
+ Studio 2015). All timings were produced on a i5-3570 processor.