Keys, cursors, and wrappers

The package has three state models for three common ownership patterns. Choose the state model before writing the stochastic code.

Immutable keys

Use a key when work has stable names or indices. The same operation on the same key returns the same result.

using ImmutableRNGs, Random

key = Philox4x32(42)
first_draw = rand(key)
second_draw = rand(key)

first_draw == second_draw
true

Derive a new key for new random work. splitrng creates a fixed group. subrng maps an application address to one key.

left, right = splitrng(key)
trial_17 = subrng(left, 17)

(left != right, rand(trial_17) == rand(subrng(left, 17)))
(true, true)

Sequential cursors

Use RNGCursor when call order defines one sequence. Each call returns a value and the next cursor, so state changes remain explicit.

cursor = RNGCursor(right)
x, cursor = nextrand(cursor)
z, cursor = nextrandn(cursor)

(x, z, cursor_position(cursor))
(0.18103236415138924, -1.5750850148063078, 0x00000000000000000000000000000004)

Copying a cursor replays its suffix. Pass the returned cursor forward unless replay is intentional.

Mutable wrappers

Use MutableRNG for code that requires Random.AbstractRNG. The wrapper owns a cursor. freeze returns the cursor and omits the private cache.

rng = MutableRNG(RNGCursor(right))
x_mutable = rand(rng)
z_mutable = randn(rng)
checkpoint = freeze(rng)

(x_mutable == x, z_mutable == z, checkpoint == cursor)
(true, true, true)

The wrapper adapts a cursor to mutable code. Give each sequential chain its own derived key and wrapper.

See the complete 01_basics.jl and 12_step_verbs.jl examples.