Random number generation#
Sequince makes the source and ownership of randomness explicit so runs can be
reproduced, resumed, and executed with different array backends. Every
stochastic step receives its random-number generator from the runner; custom
steps should use that object rather than global functions such as
numpy.random.random.
Eager execution#
The rng argument accepted by
run() may be:
Value |
Behaviour |
|---|---|
An integer seed |
Creates a NumPy-backed ORNG generator. Reusing the seed for an otherwise identical run reproduces the random stream. |
A |
Wraps and advances that mutable generator. |
An |
Uses the supplied generator directly, including its configured backend. |
|
Creates a fresh NumPy-backed generator without a user-specified seed. |
For a simple reproducible run, pass an integer:
result = smc.run(initial_state, rng=42, metadata=metadata)
When initialisation is also random, create one generator and pass the same object onward. The runner continues from its current position:
import numpy as np
rng = np.random.default_rng(42)
initial_particles = rng.normal(size=(500, 2))
initial_state = ParticleState(initial_particles)
result = smc.run(initial_state, rng=rng, metadata=metadata)
The generator is mutable in eager execution. Supplying the same generator object to another operation continues its stream; it does not restart from the original seed. To reproduce a run, recreate both the initial state and runner inputs from the same seed and configuration.
RNG in custom steps#
An eager step receives an orng.RandomGenerator as its third argument:
from sequince.steps.base import SMCStep
class GaussianJitter(SMCStep):
def apply(self, state, context, rng):
noise = rng.normal(size=state.particles.shape)
particles = state.particles + 0.01 * noise
return state.with_updates(particles=particles), context, None
Do not create a new fixed-seed generator inside apply. Doing so restarts the
same random sequence on every iteration and bypasses checkpointed RNG state.
Functional and JAX execution#
Functional execution does not mutate a generator. Pass backend-native state as
random_state and retain the advanced state returned by the runner:
key = jax.random.key(42)
result, key = smc.run_functional(
initial_state,
random_state=key,
metadata=metadata,
)
Each functional step accepts the current random state and must return its replacement. Reusing the input key instead of the returned key repeats the same random stream. See JAX and functional execution for compiling the functional iteration and splitting a key for random particle initialisation.
Checkpointing and resuming#
Checkpoint writers save RNG state by default. When that state is present,
resume_from restores it and ignores any replacement RNG supplied to the
resume call. This makes a split run follow the same random stream as an
uninterrupted run.
With save_rng=False, pass rng to resume_from to deliberately start
the continuation from a new random state. Once a runtime has been prepared,
pass only runtime to run; RNG and context arguments cannot be supplied
again. See Checkpointing for the complete resume workflow.
Random posterior draws#
posterior_samples() also accepts a seed,
NumPy generator, or ORNG generator. Pass one explicitly when the resampled
posterior array must be reproducible:
samples = result.posterior_samples(1_000, rng=42)