Splittable Determinism: Reproducible Colors
Gay.jl uses splittable random number generators from SplittableRandoms.jl to generate colors that are:
- Deterministic — same seed always produces same colors
- Parallelizable — independent streams for concurrent execution
- Random-access — jump to any position without iteration
This pattern originates from Pigeons.jl's Strong Parallelism Invariance (SPI) — the same principle used for reproducible MCMC sampling in black hole imaging with Comrade.jl.
Setup
using GayBasic Determinism
The fundamental property: same seed → same colors, always.
gay_seed!(42)
c1 = next_color()
c2 = next_color()
c3 = next_color()
gay_seed!(42) # Reset to same seed
@assert next_color() == c1 # Identical!
@assert next_color() == c2
@assert next_color() == c3
println("◆ Determinism verified: seed 42 always produces the same sequence")◆ Determinism verified: seed 42 always produces the same sequenceThe Splittable RNG Model
Unlike traditional RNGs that maintain sequential state, splittable RNGs create independent child streams via split. Each color operation internally splits the RNG:
seed(42) → rng₀
├── split → rng₁ → color₁
├── split → rng₂ → color₂
└── split → rng₃ → color₃This means execution order doesn't matter — color_at(3) gives the same result whether we computed colors 1 and 2 first or jumped directly.
Random Access by Index
Access any color in the sequence without computing predecessors:
c_1 = color_at(1)
c_42 = color_at(42)
c_1000 = color_at(1000)
println("Color at index 1: ", c_1)
println("Color at index 42: ", c_42)
println("Color at index 1000: ", c_1000)Color at index 1: RGB{Float64}(0.3333333432674408,0.6901960968971252,0.9019607901573181)
Color at index 42: RGB{Float64}(0.8666666746139526,0.21960784494876862,0.6039215922355652)
Color at index 1000: RGB{Float64}(0.6352941393852234,0.16078431904315948,0.8705882430076599)Verify consistency:
@assert color_at(42) == c_42 # Same color, always
@assert color_at(1) == c_1Batch Access
Efficiently retrieve colors at multiple indices:
indices = [1, 10, 100, 500, 1000]
batch = colors_at(indices)
println("\nBatch colors at indices $indices:")
show_palette(batch)
Batch colors at indices [1, 10, 100, 500, 1000]:
████ #55B0E6 ████ #7278C0 ████ #B88FEB ████ #A2C309 ████ #A229DEPalettes at Specific Seeds
Generate a visually distinct palette starting at any index:
palette_5_at_1 = palette_at(1, 5) # 5-color palette at index 1
palette_5_at_100 = palette_at(100, 5) # 5-color palette at index 100
println("\nPalette (5 colors) at index 1:")
show_palette(palette_5_at_1)
println("Palette (5 colors) at index 100:")
show_palette(palette_5_at_100)
Palette (5 colors) at index 1:
████ #FFE0FF ████ #0F0B00 ████ #00A35A ████ #0076FF ████ #FFC13C
Palette (5 colors) at index 100:
████ #AC0000 ████ #FFB600 ████ #00A5F6 ████ #003B00 ████ #00C097Why This Matters
Reproducible Visualizations
Your plots will look identical across:
- Different machines
- Different Julia sessions
- Parallel vs sequential execution
Shareable Seeds
Share a seed number and index to communicate exact colors:
# "Use color at index 137 with seed 2017"
gay_seed!(2017)
the_color = color_at(137)Debugging
When a visualization looks wrong, reproduce the exact state:
gay_seed!(problematic_seed)
# Now step through color generation to find the issueRNG State Inspection
gay_seed!(1337)
for _ in 1:5
next_color()
end
state = gay_rng_state()
println("\nRNG state after 5 colors:")
println(" Seed: ", state.seed)
println(" Invocation: ", state.invocation)
RNG state after 5 colors:
Seed: 1337
Invocation: 5Connection to Pigeons.jl
The splittable RNG pattern comes from parallel tempering MCMC:
| Pigeons.jl (MCMC) | Gay.jl (Colors) |
|---|---|
explorer.rng | gay_rng() |
split(rng) | gay_split() |
| Reproducible chains | Reproducible palettes |
| Fork-safe sampling | Fork-safe color gen |
Both use the same mathematical foundation: SplittableRandoms.jl
println("\n◆ Splittable determinism example complete")
◆ Splittable determinism example complete