Cosmetic changes after some sleep; no change in semantics.

This commit is contained in:
Tim Peters 2001-01-25 20:25:57 +00:00
parent a0ac40c530
commit cd80410854
1 changed files with 107 additions and 89 deletions

View File

@ -27,7 +27,10 @@ Translated from anonymously contributed C/C++ source.
Multi-threading note: the random number generator used here is not Multi-threading note: the random number generator used here is not
thread-safe; it is possible that two calls return the same random thread-safe; it is possible that two calls return the same random
value. value. But you can instantiate a different instance of Random() in
each thread to get generators that don't share state, then use
.setstate() and .jumpahead() to move the generators to disjoint
segments of the full period.
""" """
# XXX The docstring sucks. # XXX The docstring sucks.
@ -71,9 +74,11 @@ class Random:
self.seed(x) self.seed(x)
self.gauss_next = None self.gauss_next = None
## -------------------- core generator -------------------
# Specific to Wichmann-Hill generator. Subclasses wishing to use a # Specific to Wichmann-Hill generator. Subclasses wishing to use a
# different core generator should override the seed(), random(), # different core generator should override the seed(), random(),
# getstate(), setstate(), and jumpahead() methods. # getstate(), setstate() and jumpahead() methods.
def __whseed(self, x=0, y=0, z=0): def __whseed(self, x=0, y=0, z=0):
"""Set the Wichmann-Hill seed from (x, y, z). """Set the Wichmann-Hill seed from (x, y, z).
@ -96,66 +101,6 @@ class Random:
# Zero is a poor seed, so substitute 1 # Zero is a poor seed, so substitute 1
self._seed = (x or 1, y or 1, z or 1) self._seed = (x or 1, y or 1, z or 1)
def seed(self, a=None):
"""Seed from hashable value
None or no argument seeds from current time.
"""
if a is None:
self.__whseed()
return
a = hash(a)
a, x = divmod(a, 256)
a, y = divmod(a, 256)
a, z = divmod(a, 256)
x = (x + a) % 256 or 1
y = (y + a) % 256 or 1
z = (z + a) % 256 or 1
self.__whseed(x, y, z)
def getstate(self):
"""Return internal state; can be passed to setstate() later."""
return self.VERSION, self._seed, self.gauss_next
def __getstate__(self): # for pickle
return self.getstate()
def setstate(self, state):
"""Restore internal state from object returned by getstate()."""
version = state[0]
if version == 1:
version, self._seed, self.gauss_next = state
else:
raise ValueError("state with version %s passed to "
"Random.setstate() of version %s" %
(version, self.VERSION))
def __setstate__(self, state): # for pickle
self.setstate(state)
def jumpahead(self, n):
"""Act as if n calls to random() were made, but quickly.
n is an int, greater than or equal to 0.
Example use: If you have 2 threads and know that each will
consume no more than a million random numbers, create two Random
objects r1 and r2, then do
r2.setstate(r1.getstate())
r2.jumpahead(1000000)
Then r1 and r2 will use guaranteed-disjoint segments of the full
period.
"""
if not n >= 0:
raise ValueError("n must be >= 0")
x, y, z = self._seed
x = int(x * pow(171, n, 30269)) % 30269
y = int(y * pow(172, n, 30307)) % 30307
z = int(z * pow(170, n, 30323)) % 30323
self._seed = x, y, z
def random(self): def random(self):
"""Get the next random number in the range [0.0, 1.0).""" """Get the next random number in the range [0.0, 1.0)."""
@ -187,6 +132,75 @@ class Random:
# never return 0.0 (asserted by Tim; proof too long for a comment). # never return 0.0 (asserted by Tim; proof too long for a comment).
return (x/30269.0 + y/30307.0 + z/30323.0) % 1.0 return (x/30269.0 + y/30307.0 + z/30323.0) % 1.0
def seed(self, a=None):
"""Seed from hashable object's hash code.
None or no argument seeds from current time. It is not guaranteed
that objects with distinct hash codes lead to distinct internal
states.
"""
if a is None:
self.__whseed()
return
a = hash(a)
a, x = divmod(a, 256)
a, y = divmod(a, 256)
a, z = divmod(a, 256)
x = (x + a) % 256 or 1
y = (y + a) % 256 or 1
z = (z + a) % 256 or 1
self.__whseed(x, y, z)
def getstate(self):
"""Return internal state; can be passed to setstate() later."""
return self.VERSION, self._seed, self.gauss_next
def setstate(self, state):
"""Restore internal state from object returned by getstate()."""
version = state[0]
if version == 1:
version, self._seed, self.gauss_next = state
else:
raise ValueError("state with version %s passed to "
"Random.setstate() of version %s" %
(version, self.VERSION))
def jumpahead(self, n):
"""Act as if n calls to random() were made, but quickly.
n is an int, greater than or equal to 0.
Example use: If you have 2 threads and know that each will
consume no more than a million random numbers, create two Random
objects r1 and r2, then do
r2.setstate(r1.getstate())
r2.jumpahead(1000000)
Then r1 and r2 will use guaranteed-disjoint segments of the full
period.
"""
if not n >= 0:
raise ValueError("n must be >= 0")
x, y, z = self._seed
x = int(x * pow(171, n, 30269)) % 30269
y = int(y * pow(172, n, 30307)) % 30307
z = int(z * pow(170, n, 30323)) % 30323
self._seed = x, y, z
## ---- Methods below this point do not need to be overridden when
## ---- subclassing for the purpose of using a different core generator.
## -------------------- pickle support -------------------
def __getstate__(self): # for pickle
return self.getstate()
def __setstate__(self, state): # for pickle
self.setstate(state)
## -------------------- integer methods -------------------
def randrange(self, start, stop=None, step=1, int=int, default=None): def randrange(self, start, stop=None, step=1, int=int, default=None):
"""Choose a random item from range(start, stop[, step]). """Choose a random item from range(start, stop[, step]).
@ -227,14 +241,15 @@ class Random:
return istart + istep*int(self.random() * n) return istart + istep*int(self.random() * n)
def randint(self, a, b): def randint(self, a, b):
"""Get a random integer in the range [a, b] including """Return random integer in range [a, b], including both end points.
both end points.
(Deprecated; use randrange below.) (Deprecated; use randrange(a, b+1).)
""" """
return self.randrange(a, b+1) return self.randrange(a, b+1)
## -------------------- sequence methods -------------------
def choice(self, seq): def choice(self, seq):
"""Choose a random element from a non-empty sequence.""" """Choose a random element from a non-empty sequence."""
return seq[int(self.random() * len(seq))] return seq[int(self.random() * len(seq))]
@ -254,17 +269,19 @@ class Random:
if random is None: if random is None:
random = self.random random = self.random
for i in xrange(len(x)-1, 0, -1): for i in xrange(len(x)-1, 0, -1):
# pick an element in x[:i+1] with which to exchange x[i] # pick an element in x[:i+1] with which to exchange x[i]
j = int(random() * (i+1)) j = int(random() * (i+1))
x[i], x[j] = x[j], x[i] x[i], x[j] = x[j], x[i]
# -------------------- uniform distribution ------------------- ## -------------------- real-valued distributions -------------------
## -------------------- uniform distribution -------------------
def uniform(self, a, b): def uniform(self, a, b):
"""Get a random number in the range [a, b).""" """Get a random number in the range [a, b)."""
return a + (b-a) * self.random() return a + (b-a) * self.random()
# -------------------- normal distribution -------------------- ## -------------------- normal distribution --------------------
def normalvariate(self, mu, sigma): def normalvariate(self, mu, sigma):
# mu = mean, sigma = standard deviation # mu = mean, sigma = standard deviation
@ -284,12 +301,12 @@ class Random:
break break
return mu + z*sigma return mu + z*sigma
# -------------------- lognormal distribution -------------------- ## -------------------- lognormal distribution --------------------
def lognormvariate(self, mu, sigma): def lognormvariate(self, mu, sigma):
return _exp(self.normalvariate(mu, sigma)) return _exp(self.normalvariate(mu, sigma))
# -------------------- circular uniform -------------------- ## -------------------- circular uniform --------------------
def cunifvariate(self, mean, arc): def cunifvariate(self, mean, arc):
# mean: mean angle (in radians between 0 and pi) # mean: mean angle (in radians between 0 and pi)
@ -297,7 +314,7 @@ class Random:
return (mean + arc * (self.random() - 0.5)) % _pi return (mean + arc * (self.random() - 0.5)) % _pi
# -------------------- exponential distribution -------------------- ## -------------------- exponential distribution --------------------
def expovariate(self, lambd): def expovariate(self, lambd):
# lambd: rate lambd = 1/mean # lambd: rate lambd = 1/mean
@ -309,7 +326,7 @@ class Random:
u = random() u = random()
return -_log(u)/lambd return -_log(u)/lambd
# -------------------- von Mises distribution -------------------- ## -------------------- von Mises distribution --------------------
def vonmisesvariate(self, mu, kappa): def vonmisesvariate(self, mu, kappa):
# mu: mean angle (in radians between 0 and 2*pi) # mu: mean angle (in radians between 0 and 2*pi)
@ -351,7 +368,7 @@ class Random:
return theta return theta
# -------------------- gamma distribution -------------------- ## -------------------- gamma distribution --------------------
def gammavariate(self, alpha, beta): def gammavariate(self, alpha, beta):
# beta times standard gamma # beta times standard gamma
@ -410,7 +427,7 @@ class Random:
return x return x
# -------------------- Gauss (faster alternative) -------------------- ## -------------------- Gauss (faster alternative) --------------------
def gauss(self, mu, sigma): def gauss(self, mu, sigma):
@ -443,7 +460,7 @@ class Random:
return mu + z*sigma return mu + z*sigma
# -------------------- beta -------------------- ## -------------------- beta --------------------
def betavariate(self, alpha, beta): def betavariate(self, alpha, beta):
@ -453,7 +470,7 @@ class Random:
z = self.expovariate(1.0/beta) z = self.expovariate(1.0/beta)
return z/(y+z) return z/(y+z)
# -------------------- Pareto -------------------- ## -------------------- Pareto --------------------
def paretovariate(self, alpha): def paretovariate(self, alpha):
# Jain, pg. 495 # Jain, pg. 495
@ -461,7 +478,7 @@ class Random:
u = self.random() u = self.random()
return 1.0 / pow(u, 1.0/alpha) return 1.0 / pow(u, 1.0/alpha)
# -------------------- Weibull -------------------- ## -------------------- Weibull --------------------
def weibullvariate(self, alpha, beta): def weibullvariate(self, alpha, beta):
# Jain, pg. 499; bug fix courtesy Bill Arms # Jain, pg. 499; bug fix courtesy Bill Arms
@ -469,7 +486,7 @@ class Random:
u = self.random() u = self.random()
return alpha * pow(-_log(u), 1.0/beta) return alpha * pow(-_log(u), 1.0/beta)
# -------------------- test program -------------------- ## -------------------- test program --------------------
def _test_generator(n, funccall): def _test_generator(n, funccall):
import time import time
@ -493,17 +510,6 @@ def _test_generator(n, funccall):
print 'avg %g, stddev %g, min %g, max %g' % \ print 'avg %g, stddev %g, min %g, max %g' % \
(avg, stddev, smallest, largest) (avg, stddev, smallest, largest)
s = getstate()
N = 1019
jumpahead(N)
r1 = random()
setstate(s)
for i in range(N): # now do it the slow way
random()
r2 = random()
if r1 != r2:
raise ValueError("jumpahead test failed " + `(N, r1, r2)`)
def _test(N=200): def _test(N=200):
print 'TWOPI =', TWOPI print 'TWOPI =', TWOPI
print 'LOG4 =', LOG4 print 'LOG4 =', LOG4
@ -526,6 +532,18 @@ def _test(N=200):
_test_generator(N, 'paretovariate(1.0)') _test_generator(N, 'paretovariate(1.0)')
_test_generator(N, 'weibullvariate(1.0, 1.0)') _test_generator(N, 'weibullvariate(1.0, 1.0)')
# Test jumpahead.
s = getstate()
jumpahead(N)
r1 = random()
# now do it the slow way
setstate(s)
for i in range(N):
random()
r2 = random()
if r1 != r2:
raise ValueError("jumpahead test failed " + `(N, r1, r2)`)
# Initialize from current time. # Initialize from current time.
_inst = Random() _inst = Random()
seed = _inst.seed seed = _inst.seed