random – Random number functionality#

The pytensor.tensor.random module provides random-number drawing functionality that closely resembles the numpy.random module.

High-level API#

PyTensor uses RandomGeneratorVariables (symbolic equivalent to NumPy random Generators) to define graphs with pseudo-random draws.

Unlike NumPy, each random-draw definition returns a tuple of (next_rng, draws), containing the symbolic draws and updated random Generator state.

Users should use next_rng for subsequent random draw operations, and for the final update expression.

For an example of how to use random numbers, see Using Random Numbers.

For a technical explanation of how PyTensor implements random variables see Pseudo random number generation in PyTensor.

Creating shared RNG variables#

We recommend users work with shared rngs, and use updates machinery to automatically update the rng contents at the end of each function call.

pytensor.tensor.random.variable.shared_rng(value=None, *, seed=<object object>, name=None, borrow=False)[source]#

Create a shared random number generator variable.

The RNG state is stored internally and can be updated across function calls via the updates parameter of pytensor.function.

Parameters:
  • value (numpy.random.Generator, optional) – The initial RNG state. If None, a new numpy.random.default_rng(seed) is used.

  • seed (int, optional) – Seed for the default RNG. Only used when value is None.

  • name (str, optional) – Name for the shared variable.

  • borrow (bool) – If True, the shared variable will use the provided value directly without copying.

Return type:

RandomGeneratorSharedVariable

Examples

>>> import numpy as np
>>> import pytensor
>>> import pytensor.tensor.random as ptr

Create from an existing Generator:

>>> rng = ptr.shared_rng(np.random.default_rng(153), name="rng")
>>> next_rng, x = rng.normal()
>>> fn = pytensor.function([], x, updates={rng: next_rng})
>>> fn()
array(1.45769255)

Or create from a seed directly:

>>> rng2 = ptr.shared_rng(seed=153, name="rng2")
>>> next_rng2, x2 = rng2.normal()
>>> fn2 = pytensor.function([], x2, updates={rng2: next_rng2})
>>> fn2()
array(1.45769255)

Use set_value to reset the RNG state:

>>> rng.set_value(np.random.default_rng(153))
>>> fn()
array(1.45769255)
>>> rng.set_value(seed=153)
>>> fn()
array(1.45769255)
init_rng = pt.random.shared_rng(seed=123, name="rng")
next_rng, x = init_rng.uniform(-1, 1)
final_rng, y = next_rng.normal(0, 1)

fn = pytensor.function([], [x, y], updates={init_rng: final_rng})
x_draw1, y_draw1 = fn()
x_draw2, y_draw2 = fn()
assert x_draw1 != y_draw1 != x_draw2 != y_draw2
init_rng.set_value(seed=123)
x_draw3, y_draw3 = fn()
assert x_draw3 == x_draw1
assert y_draw3 == y_draw1

Warning

The same RNG variable should never be used for multiple operations, as it will produce correlated draws. Always use the newly returned rng. See rng_reuse_warning for details.

Not respecting this will trigger a warning.

Using RNG variables#

If a user prefers to work with non-shared RNG variables, they can do so with

pytensor.tensor.random.variable.rng(name=None)[source]#

Create a symbolic random number generator variable.

This creates a root variable with no data attached, suitable for use as a function input. When compiling a function, use pytensor.In(rng, mutable=True) to allow in-place RNG updates.

Parameters:

name (str, optional) – Name for the variable.

Returns:

A symbolic random number generator variable.

Return type:

RandomGeneratorVariable

Examples

>>> import numpy as np
>>> import pytensor
>>> import pytensor.tensor.random as ptr
>>> rng = ptr.rng("rng")
>>> next_rng, x = rng.normal()
>>> fn = pytensor.function([pytensor.In(rng, mutable=True)], [next_rng, x])
>>> rng_val = np.random.default_rng(153)
>>> rng_val, draw = fn(rng_val)
>>> draw
array(1.45769255)
>>> rng_val, draw = fn(rng_val)
>>> draw
array(0.44383835)

RandomGeneratorVariable objects have distribution methods that mirror pytensor.tensor.random functions. Each method returns a tuple of (next_rng, draw):

init_rng = pt.random.rng()
next_rng, x = init_rng.uniform(-1, 1)
final_rng, y = next_rng.normal(0, 1)

fn = pytensor.function([pytensor.In(init_rng, mutable=True)], [final_rng, x, y])

rng_np = np.random.default_rng(123)

rng_np, x_draw1, y_draw1 = fn(rng_np)
x_draw2, y_draw2 = fn(rng_np)
assert x_draw1 != y_draw1 != x_draw2 != y_draw2

rng_np = np.random.default_rng(123)
_, x_draw3, y_draw3 = fn(rng_np)
assert x_draw3 == x_draw1
assert y_draw3 == y_draw1

Note

Use pytensor.In(..., mutable=True) on initial rng variables to allow direct inplace use by PyTensor. Otherwise a costly deepcopy will be perfomed on the node that first uses it. Alternatively using shared_rng and working with updates grants the same inplace permissions.

See also

Using Random Numbers for a tutorial with practical examples.

Pseudo random number generation in PyTensor for a deeper look at how PyTensor implements RNG state threading, inplace optimizations, and backend support.

Distributions#

All distributions are available as methods on RandomGeneratorVariable (e.g. rng.normal()) and as module-level functions in pytensor.tensor.random.

class pytensor.tensor.random.variable.RandomGeneratorVariable(type, owner, index=None, name=None)[source]#

The Variable type used for random number generator states.