Rule 30 RNG
April 29, 2011
We begin with some global variables. Size
is the length of a row; research shows that an odd length is slightly better than an even length, and we want at least 200 cells in the row. Result
is the index of the central cell that will be the source of the output bits. State
is a vector that holds the current row of the generator:
(define size (* 7 43))
(define result (quotient size 2))
(define state (make-vector size 0))
We begin with the function that steps from one state to the next, returning the result bit and updating the state as a side effect. We implement Rule 30 by adding the bit-values of the three neighboring cells as a binary number, then looking up the new state. The two if
statements implement the wrap-around, and the rule30
vector holds the bits of a binary 30 in reverse order:
(define rule30 #(0 1 1 1 1 0 0 0))
(define (step)
(let ((next (make-vector size 0)))
(do ((i 0 (+ i 1))) ((= i size))
(vector-set! next i
(vector-ref rule30
(+ (* 4 (if (zero? i)
(vector-ref state (- size 1))
(vector-ref state (- i 1))))
(* 2 (vector-ref state i))
(if (= i (- size 1))
(vector-ref state 0)
(vector-ref state (+ i 1)))))))
(set! state next)
(vector-ref state result)))
If you are clever, you can operate on bits in a block as long as the longest integer type available, which is much faster than operating on each bit separately. But that requires special handling at the ends of the blocks, and isn’t really feasible in Scheme, even with the bitwise operators of R6RS Scheme, because there are no native bit-vectors. Thus we use the bit-at-a-time method.
Step
makes it is easy to get
successive states, accumulating them until the desired number is reached:
(define (get n)
(let loop ((n n) (xs '()))
(if (zero? n)
(undigits (reverse xs) 2)
(loop (- n 1) (cons (step) xs)))))
There is no pre-defined way to initialize the state
, so we invent our own. We’ll take a pass phrase, like Programming Praxis
, expand it as a base-256 number modulo the block size, and copy it into each initial block of the state. Init-state
takes one or more numbers, each the bits for a block, and cycles if there aren’t enough blocks given. Note that we run the generator for a little while (state size times the number of blocks) to get it “warmed up” for use. The block size must be a divisor of the state size:
(define block 43) ; must be a divisor of size
(define (init-state . xs)
(set! state (make-vector size 0))
(do ((i (- block 1) (+ i block))
(xs (cycle xs) (cdr xs)))
((< size i))
(do ((j i (- j 1))
(ds (reverse (digits (car xs) 2)) (cdr ds)))
((null? ds))
(vector-set! state j (car ds))))
(do ((i 0 (+ i 1))) ((< (* size size (/ block)) i))
(step)))
(define (pass n str)
(let loop ((ss (string->list str)) (sum 0))
(if (null? ss) sum
(loop (cdr ss)
(modulo (+ sum (* sum 256)
(char->integer (car ss)))
(expt 2 n))))))
(init-state (pass block "Programming Praxis"))
In practice, you would probably want the block size to be either 32 or 64, depending on the native word size for your machine; 256 makes a good state size. On the other hand, there is some evidence that odd state sizes make better random number generators. We choose a block size of 43 and state size of 7 × 43 = 301. For testing, you can call (init-state 0)
to clear the state, then (vector-set! state result 1)
turns on a single bit in the center of the state, as Wolfram did in his book; then the next 32 bits are 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, and 0 (Sloane’s A051023), which you can see by saying (get 32)
, which will return 3112904540.
We used cycle
, digits
and undigits
from the Standard Prelude. You can run the program at http://programmingpraxis.codepad.org/zoGntTfu.
[…] today’s Programming Praxis exercise, our goal is to write a random number generator based on the Rule 30 […]
My Haskell solution (see http://bonsaicode.wordpress.com/2011/04/29/programming-praxis-rule-30-rng/ for a version with comments):
Don’t really know if I understand algorithm, but here is my attempt in python:
Not the most efficient, but I wanted to look at python’s bitwise capabilities and to play with all that funky stuff Graham uses all the time :D
Basically I use simple number as a state (each binary digit represents cell) and then use gen_state2 to generate all the preceding states.
Implemented a Rule30 class. An instance of the class can be called like a function to return the next random number. The class also supports the iteration protocol.
The basic strategy is to store the state of the rng in an integer. left and right are the state rotated 1 bit to the right and left, respectively, so the three bits that make up the next state “line up”. Then a boolean expression involving left, state and right is used to calculate the next state.
The LSBs of the state are accumulated to determine the random number
Forth version. We take a 30 bit number, prep it by shifting once left and wrapping the bits. Each iteration of the loop, use table to look up the value of current bit based on three bits in original word.
Session:
@arturasl, you flatter me. As for me, here’s my Python solution.
Perhaps not as elegant as others, but I wanted to play with Python’s bitarrays for speed.
@arturasl: I just try to make my Python solutions get anywhere near the great functional programming
we see in the “official” solution and in the Haskell ones @Remco posts :-)
That was pretty neat. I’m not sure how valuable it is as a RNG, as there are easier to code RNGs that seem more random (heck, this post from here at Programming Praxis even) but as a thought experiment it’s worthwhile. And it gave me a chance to build on the cellular automaton code I’d previously built in JavaScript and transfer that to Racket.
In any case, here’s my Racket version: Rule 30 RNG
[…] number generator in specific: a Rule 30 psuedo-random number generator (PRNG). (Here’s the motivating post from Programming […]
My own rather slow solution:
[…] built several random number generators: [1], [2], [3], [4], [5], [6], [7], [8], [9] (I didn’t realize it was so many until I went back and looked). In […]