summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorRobert Kern <robert.kern@gmail.com>2019-06-27 00:44:43 -0700
committerRobert Kern <robert.kern@gmail.com>2019-06-27 00:44:43 -0700
commited723197a302fbbff6032aab0ee63a0d6b3a6706 (patch)
tree79ae29aba917dd0363d7947ee51dac45ca2b11f4 /doc
parentb976458713fbbe3b282792ad10500693844bfeec (diff)
downloadnumpy-ed723197a302fbbff6032aab0ee63a0d6b3a6706.tar.gz
DOC: np.random documentation cleanup and expansion.
Diffstat (limited to 'doc')
-rw-r--r--doc/source/reference/random/bit_generators/index.rst89
-rw-r--r--doc/source/reference/random/index.rst55
-rw-r--r--doc/source/reference/random/legacy.rst24
-rw-r--r--doc/source/reference/random/multithreading.rst8
-rw-r--r--doc/source/reference/random/parallel.rst222
5 files changed, 243 insertions, 155 deletions
diff --git a/doc/source/reference/random/bit_generators/index.rst b/doc/source/reference/random/bit_generators/index.rst
index 4540f60d9..24ac34e21 100644
--- a/doc/source/reference/random/bit_generators/index.rst
+++ b/doc/source/reference/random/bit_generators/index.rst
@@ -17,21 +17,23 @@ 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 ``2**128`` draws have
+ function that returns a new generator with state as-if :math:`2^{128}` draws have
been made.
-* PCG-64 - Fast generator that support 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.
-* Philox - a counter-based generator capable of being advanced an
+* 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
@@ -46,26 +48,65 @@ Seeding and Entropy
-------------------
A BitGenerator provides a stream of random values. In order to generate
-reproducableis streams, BitGenerators support setting their initial state via a
-seed. But how best to seed the BitGenerator? On first impulse one would like to
-do something like ``[bg(i) for i in range(12)]`` to obtain 12 non-correlated,
-independent BitGenerators. However using a highly correlated set of seeds could
-generate BitGenerators that are correlated or overlap within a few samples.
-
-NumPy uses a `SeedSequence` class to mix the seed in a reproducible way that
-introduces the necessary entropy to produce independent and largely non-
-overlapping streams. Small seeds are unable to fill the complete range of
-initializaiton states, and lead to biases among an ensemble of small-seed
-runs. For many cases, that doesn't matter. If you just want to hold things in
-place while you debug something, biases aren't a concern. For actual
-simulations whose results you care about, let ``SeedSequence(None)`` do its
-thing and then log/print the `SeedSequence.entropy` for repeatable
-`BitGenerator` streams.
+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(f'seed = {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
- SeedSequence
bit_generator.SeedlessSeedSequence
diff --git a/doc/source/reference/random/index.rst b/doc/source/reference/random/index.rst
index f32853e7c..89dcacce3 100644
--- a/doc/source/reference/random/index.rst
+++ b/doc/source/reference/random/index.rst
@@ -9,10 +9,6 @@ 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:
-* SeedSequence: Objects that provide entropy for the initial state of a
- BitGenerator. A good SeedSequence will provide initializations across the
- entire range of possible states for the BitGenerator, otherwise biases may
- creep into the generated bit streams.
* 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
@@ -24,18 +20,18 @@ 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
+routines. The legacy `~RandomState` random number routines are still
available, but limited to a single BitGenerator.
-For convenience and backward compatibility, a single `RandomState`
+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 normals provided by `PCG64` which will be
-statistically more reliable than the legacy methods in `RandomState`
+By default, `~Generator` uses normals provided by `~PCG64` which will be
+statistically more reliable than the legacy methods in `~RandomState`
.. code-block:: python
@@ -43,9 +39,9 @@ statistically more reliable than the legacy methods in `RandomState`
from numpy import random
random.standard_normal()
-`Generator` can be used as a direct replacement for `~RandomState`, although
+`~Generator` can be used as a direct replacement for `~RandomState`, although
the random values are generated by `~PCG64`. The
-`Generator` holds an instance of a BitGenerator. It is accessible as
+`~Generator` holds an instance of a BitGenerator. It is accessible as
``gen.bit_generator``.
.. code-block:: python
@@ -69,25 +65,14 @@ is wrapped with a `~.Generator`.
Introduction
------------
-RandomGen takes a different approach to producing random numbers from the
-`RandomState` object. Random number generation is separated into three
-components, a seed sequence, a bit generator and a random generator.
+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 `SeedSequence` takes a seed and provides the initial state for the
-`BitGenerator`. Since consecutive seeds can cause bad effects when comparing
-`BitGenerator` streams, the `SeedSequence` uses current best-practice methods
-to spread the initial state out. However small seeds may still be unable to
-reach all possible initialization states, which can cause biases among an
-ensemble of small-seed runs. For many cases, that doesn't matter. If you just
-want to hold things in place while you debug something, biases aren't a
-concern. For actual simulations whose results you care about, let
-``SeedSequence(None)`` do its thing and then log/print the
-`SeedSequence.entropy` for repeatable `BitGenerator` streams.
-
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
@@ -95,19 +80,22 @@ 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
-`~mt19937.MT19937` bit generator, the underlying bit generator in Python -- as
-the sole argument. Note that the BitGenerator must be instantiated.
+`~pcg64.PCG64` bit generator as the sole argument.
+
.. code-block:: python
- from numpy.random import Generator, PCG64
- rg = Generator(PCG64())
+ from numpy.random import default_gen
+ rg = default_gen(12345)
rg.random()
-Seed information is directly passed to the bit generator.
+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
- rg = Generator(PCG64(12345))
+ from numpy.random import Generator, MT19937
+ rg = Generator(MT19937(12345))
rg.random()
What's New or Different
@@ -146,6 +134,8 @@ What's New or Different
* `~.Generator.random` is now the canonical way to generate floating-point
random numbers, which replaces `random_sample`, `sample`, and `ranf`. This
is consistent with Python's `random.random`.
+* All BitGenerators in numpy use `~SeedSequence` to process convert seeds into
+ initialized states.
See :ref:`new-or-different` for a complete list of improvements and
differences from the traditional ``Randomstate``.
@@ -154,10 +144,11 @@ Parallel Generation
~~~~~~~~~~~~~~~~~~~
The included generators can be used in parallel, distributed applications in
-one of two ways:
+one of three ways:
+* :ref:`seedsequence-spawn`
* :ref:`independent-streams`
-* :ref:`jump-and-advance`
+* :ref:`parallel-jumped`
Concepts
--------
diff --git a/doc/source/reference/random/legacy.rst b/doc/source/reference/random/legacy.rst
index d9391e9e2..1f022180f 100644
--- a/doc/source/reference/random/legacy.rst
+++ b/doc/source/reference/random/legacy.rst
@@ -1,3 +1,5 @@
+.. currentmodule:: numpy.random
+
.. _legacy:
Legacy Random Generation
@@ -8,7 +10,7 @@ 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 NumPy.
+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
@@ -16,18 +18,12 @@ 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.
-.. warning::
-
- :class:`~randomgen.legacy.LegacyGenerator` only contains functions
- that have changed. Since it does not contain other functions, it
- is not directly 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`.
-
+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`.
.. code-block:: python
@@ -39,7 +35,7 @@ are produced in pairs. It is important to use
mt19937 = MT19937(12345)
lg = RandomState(mt19937)
- # Identical output
+ # Different output, sorry.
rs.standard_normal()
lg.standard_normal()
diff --git a/doc/source/reference/random/multithreading.rst b/doc/source/reference/random/multithreading.rst
index 849d64d4e..6883d3672 100644
--- a/doc/source/reference/random/multithreading.rst
+++ b/doc/source/reference/random/multithreading.rst
@@ -1,9 +1,11 @@
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
+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.
diff --git a/doc/source/reference/random/parallel.rst b/doc/source/reference/random/parallel.rst
index 36e173ef2..18060defe 100644
--- a/doc/source/reference/random/parallel.rst
+++ b/doc/source/reference/random/parallel.rst
@@ -5,59 +5,147 @@ 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
--------------------
+.. _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_gen
-: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.
+ ss = SeedSequence(12345)
+
+ # Spawn off 10 child SeedSequences to pass to child processes.
+ child_seeds = ss.spawn(10)
+ streams = [default_gen(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
- from numpy.random.entropy import random_entropy
- from numpy.random import PCG64
+ grandchildren = child_seeds[0].spawn(4)
+ grand_streams = [default_gen(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
- 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)]
+.. _independent-streams:
+
+Independent Streams
+-------------------
-: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.
+: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 numpy as np
- from numpy.random import ThreeFry
+ import secrets
+ from numpy.random import Philox
- 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)]
+ # 128-bit number as a seed
+ root_seed = secrets.getrandbits(128)
+ streams = [Philox(key=root_seed + stream_id) for stream_id in range(10)]
-.. _jump-and-advance:
+.. end_block
-Jump/Advance the BitGenerator state
------------------------------------
+This scheme does require that you avoid reusing stream IDs. This may require
+coordination between the parallel processes.
-Jumped
-******
+
+.. _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^{512}`. Additionally, the *as-if* draws also depend
+: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 BitGenerator that support ``jumped``, along with the period of the
+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.
@@ -66,70 +154,40 @@ are listed below.
+=================+=========================+=========================+=========================+
| MT19937 | :math:`2^{19937}` | :math:`2^{128}` | 32 |
+-----------------+-------------------------+-------------------------+-------------------------+
-| PCG64 | :math:`2^{128}` | :math:`2^{64}` | 64 |
+| PCG64 | :math:`2^{128}` | :math:`~2^{127}` ([3]_) | 64 |
+-----------------+-------------------------+-------------------------+-------------------------+
| Philox | :math:`2^{256}` | :math:`2^{128}` | 64 |
+-----------------+-------------------------+-------------------------+-------------------------+
-| ThreeFry | :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
- from numpy.random.entropy import random_entropy
+ import secrets
from numpy.random import PCG64
- entropy = random_entropy(2).astype(np.uint64)
- # 64-bit number as a seed
- seed = entropy[0] * 2**32 + entropy[1]
+ seed = secrets.getrandbits(128)
blocked_rng = []
rng = PCG64(seed)
for i in range(10):
blocked_rng.append(rng.jumped(i))
-Advance
-*******
-``advance`` can be used to jump the state an arbitrary number of steps, and so
-is a more general approach than ``jumped``. :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 BitGenerator updates the underlying state as-if a given number of
-calls to the BitGenerator 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 BitGenerator.
-This occurs for two reasons:
-
-* The random values are simulated using a rejection-based method
- and so more than one value from the underlying BitGenerator can be 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 BitGenerator. For example, two
- 16-bit integer values can be simulated from a single draw of a 32-bit value.
-
-Advancing the BitGenerator 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
- bit_generator = PCG64()
- bit_generator_copy = PCG64()
- bit_generator_copy.state = bit_generator.state
-
- advance = 2**127
- bit_generators = [bit_generator]
- for _ in range(9):
- bit_generator_copy.advance(advance)
- bit_generator = PCG64()
- bit_generator.state = bit_generator_copy.state
- bit_generators.append(bit_generator)
-
-.. end block
+.. 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.