r250.py

Go to the documentation of this file.
00001 """
00002 r-250 random number generator... VERY fast, high quality random bit strings and floats
00003 implemented by Marcus Mendenhall, Vanderbilt University Free Electron Laser Center, nashville, TN USA
00004 Public Domain
00005 December, 2002
00006 
00007 The code which follows is a pythonization of code derived from the following two sources:
00008     
00009 DESCRIPTION:
00010 Shift-register Random Number Generator.
00011 
00012 Copyleft (c) 1987, 1992. Eugene I Levin, A.F.Ioffe institute,
00013                          St. Petersburg, Russia.
00014                          E-mail: Levin@lh.ioffe.rssi.ru
00015 
00016 This is a public domain program and may be freely distributed under the
00017 condition of remaining the original Copyleft information.
00018 
00019 The author would appreciate to be informed about any improvements of
00020 these functions, or their implementations for any other computer system.
00021 
00022 and:
00023 /* r250.c   the r250 uniform random number algorithm
00024 
00025         Kirkpatrick, S., and E. Stoll, 1981; "A Very Fast
00026         Shift-Register Sequence Random Number Generator",
00027         Journal of Computational Physics, V.40
00028 
00029         also:
00030 
00031         see W.L. Maier, DDJ May 1991
00032 
00033 
00034 
00035 */
00036 
00037 REALIZATION NOTES:
00038 This is a realization of standard shift-register algorithm for random
00039 bit streams:
00040    x[n] = x[n-q] XOR x[n-p]                                        (1)
00041 where p and q satisfy the condition that the polynomial x^p + x^q + 1
00042 must be primitive on the GF(2) field. There are a few known pairs (p,
00043 q), p = 250 and q = 103 has been used.
00044 
00045 In order to generate float numbers independent bit streams (1) are used
00046 for the each bit of mantissa.
00047 
00048 Consecutive values of (1) are stored in array "buffer" of the length p.
00049 The index of the n-th member of (1) in buffer is
00050    i = n mod p                                                     (2)
00051 so that a new value x[n] always replaces x[n-p]. Substitution of Eq. (2)
00052 into Eq. (1) yields to the algorithm:
00053    x[i] = x[i] XOR x[i - p]      when i >= p,                      (3a)
00054    x[i] = x[i] XOR x[i - p + q]  when i < p.                       (3b)
00055 
00056 The operations have been extended to include r521 and r250_521, per the recommendations of
00057 Heuer, Dunweg and Ferrenberg,
00058 "Considerations on Correlations in Shift-Register Pseudorandom Number Generators and Their Removal"
00059 by Marcus Mendenhall
00060                                                                 
00061 """
00062 _rcsid="$Id: r250.py,v 1.10 2007/08/30 14:32:15 mendenhall Exp $"
00063 
00064 import random
00065 
00066 try:
00067   import numpy as Numeric
00068   numeric_int32=Numeric.int32
00069   numeric_float=Numeric.float
00070 except:
00071   import Numeric
00072   numeric_int32=Numeric.Int32
00073   numeric_float=Numeric.Float
00074 
00075 import math
00076 
00077 class ran_shift(random.Random):
00078     "generate shift-register based high-quality randoms"    
00079     p,q = 250, 103
00080     def __init__(self, seed=None):
00081         self.floatscale=1.0/float(1L<<62)
00082         self.single_floatscale=1.0/float(1L<<31)
00083         self.seed(seed)
00084         self.mask32=Numeric.array(0x7ffffe00, numeric_int32)
00085         
00086     def seed(self, seed=None):
00087         "seed from the built in RNG python provides, and then scramble results a little"
00088         random.Random.seed(self, seed)
00089         seeds=[ 
00090             int( ((long(math.floor(random.random()*32768.0)) << 16) & 0x7fff0000) | long(math.floor(random.random()*65536.0))) for i in range(self.p)]
00091         self.ranbuf=Numeric.array(seeds, numeric_int32)
00092         
00093         for i in range(self.p): #scramble the dubious randoms from the default generator to start the system
00094             self.regenerate()
00095         
00096     def regenerate(self):
00097         "generate the next block of randoms, quickly"
00098         #the next two lines contain a slight subtlety:
00099         #the first line involves trivially non-overlapping operations (assuming q < p/2). 
00100         #the second line involves intentionally overlapped regions, so that the operation must be proceeding in 
00101         #place to make sure that the proper recursion is really happening. 
00102         self.ranbuf[:self.q]^=self.ranbuf[-self.q:]
00103         self.ranbuf[self.q:]^=self.ranbuf[:-self.q]
00104         self.counter=0
00105 
00106     def next(self):
00107         "return the next 31 bits from the random pool"
00108         v=self.ranbuf[self.counter]
00109         self.counter+=1
00110         if self.counter==self.p:
00111             self.regenerate()
00112         return v
00113 
00114     def getstate(self):
00115         return Numeric.array(self.ranbuf), self.counter #make sure we copy the array, not just return a reference
00116     
00117     def setstate(self, state):
00118         self.ranbuf=Numeric.array(state[0]) #make a copy here, too, so we can re-use the state as often as needed
00119         self.counter = state[1]
00120         
00121     def random(self):
00122         "return a _really good_ double precision (52 bits significant) random on [0,1) (upper included bound = (1 - 2^-52) )"
00123         q1, q2 = self.next(), self.next() #take 64 bits from the random pool
00124         return float( (long(q1) << 31) | ( long(q2) & 0x7ffffe00L) ) *self.floatscale
00125 
00126     def single_float_random(self):
00127         "return a good single-precision (32 bits significant) random on [0,1)  (upper included bound = (1 - 2^-32) )"
00128         q1 = self.next() #take 32 bits from the random pool
00129         return float(q1)*self.single_floatscale
00130             
00131     def fast_random_series(self, count):
00132         "return a series of 31-bit ints much more quickly than individual calls to next()."
00133         results=Numeric.zeros(count, numeric_int32)
00134         first=min(count, self.p-self.counter) #how many are already in the buffer that we want?
00135         results[:first]=self.ranbuf[self.counter:self.counter+first]
00136         #print self.ranbuf[self.counter], results.tolist()
00137         count-=first
00138         self.counter+=first
00139         if self.counter==self.p: #used up all in buffer, start generating more
00140             self.regenerate()
00141             for i in range(count//self.p):
00142                 results[first:first+self.p]=self.ranbuf
00143                 first+=self.p
00144                 count-=self.p
00145                 self.regenerate()
00146             if count:
00147                 results[first:]=self.ranbuf[:count]
00148                 self.counter=count
00149         return results
00150 
00151     def single_float_random_series(self, count):
00152         "return a series of good single-precision-quality (actually 31 bits embedded in double) randoms on [0,1).(upper included bound = (1 - 2^-31) )  \
00153                 Note: conversion of this number to Float32 could result in rounding to exactly 1.0"
00154 
00155         #Warning! NumPy Unsigned-> Float is really signed!
00156         q1 = Numeric.array(self.fast_random_series(count), numeric_float) #take bits from the random pool. 
00157         q1*=self.single_floatscale
00158         return q1
00159 
00160     def double_float_random_series(self, count):
00161         "return a series of 52-bit significance double-precision randoms on [0,1)."
00162         q1 = self.fast_random_series(2*count) #take bits from the random pool
00163         q1[1::2] &= self.mask32#mask off low bits to prevent rounding when combined to 52-bit mantissas
00164         q1=q1*self.single_floatscale #convert results to doubles scaled on [0,1)        
00165         #now, for any generator with two-point correlations, or unknown bit lengths, this would fail horribly, but this class is safe.
00166         q1[1::2]*=self.single_floatscale #rescale LSBs 
00167         q1[0::2]+=q1[1::2] #and combine with MSBs
00168         return q1[::2]
00169 
00170 class r250(ran_shift):
00171     "generate r250 based high-quality randoms"  
00172     p,q = 250, 103
00173             
00174 class r521(ran_shift):
00175     "generate r521 based high-quality randoms"  
00176     p,q = 521, 168
00177 
00178 class r250_521(r250):
00179     "generate super-quality r250/521 randoms per Heuer, Dunweg & Ferrenberg ca. 1995"
00180     def __init__(self, seed=None):
00181         r250.__init__(self, seed)
00182         self.r521=r521(seed) #give ourself a second generator
00183         
00184     def getstate(self):
00185         return r250.getstate(self), self.r521.getstate()
00186             
00187     def setstate(self, state):
00188         r250.setstate(self, state[0])
00189         self.r521.setstate(state[1])
00190 
00191     def next(self):
00192         return r250.next(self) ^ self.r521.next()
00193     
00194     def fast_random_series(self, count):
00195         return r250.fast_random_series(self, count) ^ self.r521.fast_random_series(count)
00196             
00197 if __name__ == "__main__":
00198     r=r250_521()
00199     
00200     for i in range(20):
00201         print r.random(),
00202     print
00203     
00204     print 10*"%08lx "%tuple(r.fast_random_series(10).tolist())
00205     print
00206 
00207     state=r.getstate()
00208     
00209     print r.single_float_random_series(10)
00210     print
00211     
00212     print r.double_float_random_series(10)
00213     print
00214 
00215     r.setstate(state)
00216     print r.single_float_random_series(10)
00217     print
00218 
00219     r.setstate(state)
00220     print r.single_float_random_series(10)
00221     print
00222 
00223     import struct
00224     
00225     print "checking for uniformly filled bit in combined doubles"
00226     r.setstate(state)
00227     s=r.double_float_random_series(100).tostring()
00228     ss=struct.unpack("20L", s[:80])
00229     print (20*"%08lx ")%ss
00230     b1, b2 =0, 0
00231     for i,j in zip(ss[0::2], ss[1::2]):
00232         b1, b2 = b1 | i, b2 | j
00233     print "this should be 3fffffff ffffffff:", "%08lx %08lx" % (b1,b2)
00234     
00235     r.setstate(state)
00236     ranlistfast=r.fast_random_series(1000).tolist()
00237     r.setstate(state)
00238     ranlist=[r.next() for i in range(1000)]
00239     
00240     if ranlist != ranlistfast:
00241         print "fast series is not same as single randoms"
00242         print zip(ranlist, ranlistfast)
00243     else: print "fast series sequence is same as individual numbers"
00244 
00245     r.setstate(state)
00246     ranlistfast=r.single_float_random_series(1000).tolist()
00247     r.setstate(state)
00248     ranlist=[r.single_float_random() for i in range(1000)]
00249     
00250     if ranlist != ranlistfast:
00251         print "fast single series is not same as single randoms"
00252         print zip(ranlist, ranlistfast)
00253     else: print "fast single series sequence is same as individual numbers"
00254 
00255     r.setstate(state)
00256     ranlistfast=r.double_float_random_series(1000).tolist()
00257     r.setstate(state)
00258     ranlist=[r.random() for i in range(1000)]
00259     
00260     if ranlist != ranlistfast:
00261         print "fast double series is not same as single randoms"
00262         print zip(ranlist, ranlistfast)
00263     else: print "fast double series sequence is same as individual numbers"
00264 
00265 
00266     if 1:
00267         print "running pi-darts in 3d"
00268         cycles=100
00269         count=10000
00270         sum=0.0
00271         sum2=0.0
00272         for i in range(cycles):
00273             r1=r.single_float_random_series(3*count)
00274             r1*=r1
00275             insum=Numeric.sum(Numeric.less(r1[0::3]+r1[1::3]+r1[2::3], 1.0))
00276             pi=6.0*insum/count
00277             sum+=pi
00278             sum2+=pi*pi
00279             print pi, " ", 
00280         print
00281         print "pi estimate = %.6f +- %.6f" % (sum/cycles, math.sqrt(sum2/cycles-(sum/cycles)**2)/math.sqrt(cycles) )
00282         

Generated on Wed Nov 21 10:18:31 2007 for analysis by  doxygen 1.5.4