Addressed parallel work
A mutable stream assigns random values by execution order, which a parallel schedule may change. Immutable keys assign random values to trial numbers, particle indices, data identifiers, or other application addresses.
Derive from the work index
This simulation derives each trial key from its index.
using ImmutableRNGs, Random, Statistics
root = Philox4x32(2026)
trial(key, i) = mean(randn(subrng(key, i), 64))
serial = [trial(root, i) for i in 1:32]
reordered = Dict(i => trial(root, i) for i in reverse(1:32))
serial == [reordered[i] for i in 1:32]trueEach result depends on its trial index. A different scheduler or worker count does not change the trials.
Use ordinary mutable code inside each address
@keyed derives one wrapper from each loop-variable value. The loop body can use the standard rand* interface.
threaded = zeros(32)
@keyed rng = root Threads.@threads for i in eachindex(threaded)
threaded[i] = mean(randn(64))
end
reference = [mean(randn(MutableRNG(subrng(root, i)), 64)) for i in 1:32]
threaded == referencetrueThe address must identify the work. Use an array position only if it remains stable across ordering, filtering, and retries.
Give each generated element a key
generate permits variable work within an element. Element i receives subrng(root, i - 1), so the outer array remains addressable.
walks = generate(root, Float64, 16) do element_key
step_key, sign_key = splitrng(element_key)
step = randn(step_key)
rand(sign_key, Bool) ? step : -step
end
larger = generate(root, Float64, 32) do element_key
step_key, sign_key = splitrng(element_key)
step = randn(step_key)
rand(sign_key, Bool) ? step : -step
end
walks == larger[1:16]trueSee 03_parallel_mc.jl, 08_generate_block.jl, and 16_distributed_trials.jl.